mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 15:45:27 -05:00
This addresses issue #28975 (https://github.com/microsoft/TypeScript/issues/28975). When providing a value as a type argument, we can suggest a more specific error message: "Did you mean to use typeof T?" adds error message WIP: Detect error WIP: progress updated tests janky implementation adds test coverage around literal types being unaffected refactor out isIdentifierATypeArgument function adds test case for type alias adds test case for nested type arguments fixes linting errors merge master into branch to overwrite changes changes value as type error message This suggests 'typeof T' as a potential alternative when we give an error about using value T as a type. remove stale tests from old change Co-authored-by: John Patterson <john@johnppatterson.com>
37656 lines
2.2 MiB
37656 lines
2.2 MiB
/* @internal */
|
|
namespace ts {
|
|
const ambientModuleSymbolRegex = /^".+"$/;
|
|
const anon = "(anonymous)" as __String & string;
|
|
|
|
let nextSymbolId = 1;
|
|
let nextNodeId = 1;
|
|
let nextMergeId = 1;
|
|
let nextFlowId = 1;
|
|
|
|
const enum IterationUse {
|
|
AllowsSyncIterablesFlag = 1 << 0,
|
|
AllowsAsyncIterablesFlag = 1 << 1,
|
|
AllowsStringInputFlag = 1 << 2,
|
|
ForOfFlag = 1 << 3,
|
|
YieldStarFlag = 1 << 4,
|
|
SpreadFlag = 1 << 5,
|
|
DestructuringFlag = 1 << 6,
|
|
|
|
// Spread, Destructuring, Array element assignment
|
|
Element = AllowsSyncIterablesFlag,
|
|
Spread = AllowsSyncIterablesFlag | SpreadFlag,
|
|
Destructuring = AllowsSyncIterablesFlag | DestructuringFlag,
|
|
|
|
ForOf = AllowsSyncIterablesFlag | AllowsStringInputFlag | ForOfFlag,
|
|
ForAwaitOf = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | AllowsStringInputFlag | ForOfFlag,
|
|
|
|
YieldStar = AllowsSyncIterablesFlag | YieldStarFlag,
|
|
AsyncYieldStar = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | YieldStarFlag,
|
|
|
|
GeneratorReturnType = AllowsSyncIterablesFlag,
|
|
AsyncGeneratorReturnType = AllowsAsyncIterablesFlag,
|
|
|
|
}
|
|
|
|
const enum IterationTypeKind {
|
|
Yield,
|
|
Return,
|
|
Next,
|
|
}
|
|
|
|
interface IterationTypesResolver {
|
|
iterableCacheKey: "iterationTypesOfAsyncIterable" | "iterationTypesOfIterable";
|
|
iteratorCacheKey: "iterationTypesOfAsyncIterator" | "iterationTypesOfIterator";
|
|
iteratorSymbolName: "asyncIterator" | "iterator";
|
|
getGlobalIteratorType: (reportErrors: boolean) => GenericType;
|
|
getGlobalIterableType: (reportErrors: boolean) => GenericType;
|
|
getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType;
|
|
getGlobalGeneratorType: (reportErrors: boolean) => GenericType;
|
|
resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined;
|
|
mustHaveANextMethodDiagnostic: DiagnosticMessage;
|
|
mustBeAMethodDiagnostic: DiagnosticMessage;
|
|
mustHaveAValueDiagnostic: DiagnosticMessage;
|
|
}
|
|
|
|
const enum WideningKind {
|
|
Normal,
|
|
GeneratorYield
|
|
}
|
|
|
|
const enum TypeFacts {
|
|
None = 0,
|
|
TypeofEQString = 1 << 0, // typeof x === "string"
|
|
TypeofEQNumber = 1 << 1, // typeof x === "number"
|
|
TypeofEQBigInt = 1 << 2, // typeof x === "bigint"
|
|
TypeofEQBoolean = 1 << 3, // typeof x === "boolean"
|
|
TypeofEQSymbol = 1 << 4, // typeof x === "symbol"
|
|
TypeofEQObject = 1 << 5, // typeof x === "object"
|
|
TypeofEQFunction = 1 << 6, // typeof x === "function"
|
|
TypeofEQHostObject = 1 << 7, // typeof x === "xxx"
|
|
TypeofNEString = 1 << 8, // typeof x !== "string"
|
|
TypeofNENumber = 1 << 9, // typeof x !== "number"
|
|
TypeofNEBigInt = 1 << 10, // typeof x !== "bigint"
|
|
TypeofNEBoolean = 1 << 11, // typeof x !== "boolean"
|
|
TypeofNESymbol = 1 << 12, // typeof x !== "symbol"
|
|
TypeofNEObject = 1 << 13, // typeof x !== "object"
|
|
TypeofNEFunction = 1 << 14, // typeof x !== "function"
|
|
TypeofNEHostObject = 1 << 15, // typeof x !== "xxx"
|
|
EQUndefined = 1 << 16, // x === undefined
|
|
EQNull = 1 << 17, // x === null
|
|
EQUndefinedOrNull = 1 << 18, // x === undefined / x === null
|
|
NEUndefined = 1 << 19, // x !== undefined
|
|
NENull = 1 << 20, // x !== null
|
|
NEUndefinedOrNull = 1 << 21, // x != undefined / x != null
|
|
Truthy = 1 << 22, // x
|
|
Falsy = 1 << 23, // !x
|
|
All = (1 << 24) - 1,
|
|
// The following members encode facts about particular kinds of types for use in the getTypeFacts function.
|
|
// The presence of a particular fact means that the given test is true for some (and possibly all) values
|
|
// of that kind of type.
|
|
BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull,
|
|
BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
|
|
StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy,
|
|
StringFacts = BaseStringFacts | Truthy,
|
|
EmptyStringStrictFacts = BaseStringStrictFacts | Falsy,
|
|
EmptyStringFacts = BaseStringFacts,
|
|
NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy,
|
|
NonEmptyStringFacts = BaseStringFacts | Truthy,
|
|
BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull,
|
|
BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
|
|
NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy,
|
|
NumberFacts = BaseNumberFacts | Truthy,
|
|
ZeroNumberStrictFacts = BaseNumberStrictFacts | Falsy,
|
|
ZeroNumberFacts = BaseNumberFacts,
|
|
NonZeroNumberStrictFacts = BaseNumberStrictFacts | Truthy,
|
|
NonZeroNumberFacts = BaseNumberFacts | Truthy,
|
|
BaseBigIntStrictFacts = TypeofEQBigInt | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull,
|
|
BaseBigIntFacts = BaseBigIntStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
|
|
BigIntStrictFacts = BaseBigIntStrictFacts | Truthy | Falsy,
|
|
BigIntFacts = BaseBigIntFacts | Truthy,
|
|
ZeroBigIntStrictFacts = BaseBigIntStrictFacts | Falsy,
|
|
ZeroBigIntFacts = BaseBigIntFacts,
|
|
NonZeroBigIntStrictFacts = BaseBigIntStrictFacts | Truthy,
|
|
NonZeroBigIntFacts = BaseBigIntFacts | Truthy,
|
|
BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull,
|
|
BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
|
|
BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy,
|
|
BooleanFacts = BaseBooleanFacts | Truthy,
|
|
FalseStrictFacts = BaseBooleanStrictFacts | Falsy,
|
|
FalseFacts = BaseBooleanFacts,
|
|
TrueStrictFacts = BaseBooleanStrictFacts | Truthy,
|
|
TrueFacts = BaseBooleanFacts | Truthy,
|
|
SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy,
|
|
SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
|
|
ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy,
|
|
ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
|
|
FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy,
|
|
FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
|
|
UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy,
|
|
NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy,
|
|
EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull),
|
|
EmptyObjectFacts = All,
|
|
}
|
|
|
|
const typeofEQFacts: ReadonlyMap<TypeFacts> = createMapFromTemplate({
|
|
string: TypeFacts.TypeofEQString,
|
|
number: TypeFacts.TypeofEQNumber,
|
|
bigint: TypeFacts.TypeofEQBigInt,
|
|
boolean: TypeFacts.TypeofEQBoolean,
|
|
symbol: TypeFacts.TypeofEQSymbol,
|
|
undefined: TypeFacts.EQUndefined,
|
|
object: TypeFacts.TypeofEQObject,
|
|
function: TypeFacts.TypeofEQFunction
|
|
});
|
|
|
|
const typeofNEFacts: ReadonlyMap<TypeFacts> = createMapFromTemplate({
|
|
string: TypeFacts.TypeofNEString,
|
|
number: TypeFacts.TypeofNENumber,
|
|
bigint: TypeFacts.TypeofNEBigInt,
|
|
boolean: TypeFacts.TypeofNEBoolean,
|
|
symbol: TypeFacts.TypeofNESymbol,
|
|
undefined: TypeFacts.NEUndefined,
|
|
object: TypeFacts.TypeofNEObject,
|
|
function: TypeFacts.TypeofNEFunction
|
|
});
|
|
|
|
type TypeSystemEntity = Node | Symbol | Type | Signature;
|
|
|
|
const enum TypeSystemPropertyName {
|
|
Type,
|
|
ResolvedBaseConstructorType,
|
|
DeclaredType,
|
|
ResolvedReturnType,
|
|
ImmediateBaseConstraint,
|
|
EnumTagType,
|
|
ResolvedTypeArguments,
|
|
}
|
|
|
|
const enum CheckMode {
|
|
Normal = 0, // Normal type checking
|
|
Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable
|
|
Inferential = 1 << 1, // Inferential typing
|
|
SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions
|
|
SkipGenericFunctions = 1 << 3, // Skip single signature generic functions
|
|
IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help
|
|
}
|
|
|
|
const enum AccessFlags {
|
|
None = 0,
|
|
NoIndexSignatures = 1 << 0,
|
|
Writing = 1 << 1,
|
|
CacheSymbol = 1 << 2,
|
|
NoTupleBoundsCheck = 1 << 3,
|
|
}
|
|
|
|
const enum SignatureCheckMode {
|
|
BivariantCallback = 1 << 0,
|
|
StrictCallback = 1 << 1,
|
|
IgnoreReturnTypes = 1 << 2,
|
|
StrictArity = 1 << 3,
|
|
Callback = BivariantCallback | StrictCallback,
|
|
}
|
|
|
|
const enum IntersectionState {
|
|
None = 0,
|
|
Source = 1 << 0,
|
|
Target = 1 << 1,
|
|
}
|
|
|
|
const enum MappedTypeModifiers {
|
|
IncludeReadonly = 1 << 0,
|
|
ExcludeReadonly = 1 << 1,
|
|
IncludeOptional = 1 << 2,
|
|
ExcludeOptional = 1 << 3,
|
|
}
|
|
|
|
const enum ExpandingFlags {
|
|
None = 0,
|
|
Source = 1,
|
|
Target = 1 << 1,
|
|
Both = Source | Target,
|
|
}
|
|
|
|
const enum MembersOrExportsResolutionKind {
|
|
resolvedExports = "resolvedExports",
|
|
resolvedMembers = "resolvedMembers"
|
|
}
|
|
|
|
const enum UnusedKind {
|
|
Local,
|
|
Parameter,
|
|
}
|
|
|
|
/** @param containingNode Node to check for parse error */
|
|
type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void;
|
|
|
|
const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor);
|
|
|
|
const enum DeclarationMeaning {
|
|
GetAccessor = 1,
|
|
SetAccessor = 2,
|
|
PropertyAssignment = 4,
|
|
Method = 8,
|
|
GetOrSetAccessor = GetAccessor | SetAccessor,
|
|
PropertyAssignmentOrMethod = PropertyAssignment | Method,
|
|
}
|
|
|
|
const enum DeclarationSpaces {
|
|
None = 0,
|
|
ExportValue = 1 << 0,
|
|
ExportType = 1 << 1,
|
|
ExportNamespace = 1 << 2,
|
|
}
|
|
|
|
function SymbolLinks(this: SymbolLinks) {
|
|
}
|
|
|
|
function NodeLinks(this: NodeLinks) {
|
|
this.flags = 0;
|
|
}
|
|
|
|
export function getNodeId(node: Node): number {
|
|
if (!node.id) {
|
|
node.id = nextNodeId;
|
|
nextNodeId++;
|
|
}
|
|
return node.id;
|
|
}
|
|
|
|
export function getSymbolId(symbol: Symbol): number {
|
|
if (!symbol.id) {
|
|
symbol.id = nextSymbolId;
|
|
nextSymbolId++;
|
|
}
|
|
|
|
return symbol.id;
|
|
}
|
|
|
|
export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) {
|
|
const moduleState = getModuleInstanceState(node);
|
|
return moduleState === ModuleInstanceState.Instantiated ||
|
|
(preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly);
|
|
}
|
|
|
|
export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker {
|
|
const getPackagesSet: () => Map<true> = memoize(() => {
|
|
const set = createMap<true>();
|
|
host.getSourceFiles().forEach(sf => {
|
|
if (!sf.resolvedModules) return;
|
|
|
|
forEachEntry(sf.resolvedModules, r => {
|
|
if (r && r.packageId) set.set(r.packageId.name, true);
|
|
});
|
|
});
|
|
return set;
|
|
});
|
|
|
|
// Cancellation that controls whether or not we can cancel in the middle of type checking.
|
|
// In general cancelling is *not* safe for the type checker. We might be in the middle of
|
|
// computing something, and we will leave our internals in an inconsistent state. Callers
|
|
// who set the cancellation token should catch if a cancellation exception occurs, and
|
|
// should throw away and create a new TypeChecker.
|
|
//
|
|
// Currently we only support setting the cancellation token when getting diagnostics. This
|
|
// is because diagnostics can be quite expensive, and we want to allow hosts to bail out if
|
|
// they no longer need the information (for example, if the user started editing again).
|
|
let cancellationToken: CancellationToken | undefined;
|
|
let requestedExternalEmitHelpers: ExternalEmitHelpers;
|
|
let externalHelpersModule: Symbol;
|
|
|
|
const Symbol = objectAllocator.getSymbolConstructor();
|
|
const Type = objectAllocator.getTypeConstructor();
|
|
const Signature = objectAllocator.getSignatureConstructor();
|
|
|
|
let typeCount = 0;
|
|
let symbolCount = 0;
|
|
let enumCount = 0;
|
|
let totalInstantiationCount = 0;
|
|
let instantiationCount = 0;
|
|
let instantiationDepth = 0;
|
|
let constraintDepth = 0;
|
|
let currentNode: Node | undefined;
|
|
|
|
const emptySymbols = createSymbolTable();
|
|
const arrayVariances = [VarianceFlags.Covariant];
|
|
|
|
const compilerOptions = host.getCompilerOptions();
|
|
const languageVersion = getEmitScriptTarget(compilerOptions);
|
|
const moduleKind = getEmitModuleKind(compilerOptions);
|
|
const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions);
|
|
const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");
|
|
const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes");
|
|
const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply");
|
|
const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization");
|
|
const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny");
|
|
const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis");
|
|
const keyofStringsOnly = !!compilerOptions.keyofStringsOnly;
|
|
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral;
|
|
|
|
const emitResolver = createResolver();
|
|
const nodeBuilder = createNodeBuilder();
|
|
|
|
const globals = createSymbolTable();
|
|
const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String);
|
|
undefinedSymbol.declarations = [];
|
|
|
|
const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly);
|
|
globalThisSymbol.exports = globals;
|
|
globalThisSymbol.declarations = [];
|
|
globals.set(globalThisSymbol.escapedName, globalThisSymbol);
|
|
|
|
const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String);
|
|
const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String);
|
|
|
|
/** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */
|
|
let apparentArgumentCount: number | undefined;
|
|
|
|
// for public members that accept a Node or one of its subtypes, we must guard against
|
|
// synthetic nodes created during transformations by calling `getParseTreeNode`.
|
|
// for most of these, we perform the guard only on `checker` to avoid any possible
|
|
// extra cost of calling `getParseTreeNode` when calling these functions from inside the
|
|
// checker.
|
|
const checker: TypeChecker = {
|
|
getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"),
|
|
getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"),
|
|
getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount,
|
|
getTypeCount: () => typeCount,
|
|
getInstantiationCount: () => totalInstantiationCount,
|
|
getRelationCacheSizes: () => ({
|
|
assignable: assignableRelation.size,
|
|
identity: identityRelation.size,
|
|
subtype: subtypeRelation.size,
|
|
strictSubtype: strictSubtypeRelation.size,
|
|
}),
|
|
isUndefinedSymbol: symbol => symbol === undefinedSymbol,
|
|
isArgumentsSymbol: symbol => symbol === argumentsSymbol,
|
|
isUnknownSymbol: symbol => symbol === unknownSymbol,
|
|
getMergedSymbol,
|
|
getDiagnostics,
|
|
getGlobalDiagnostics,
|
|
getTypeOfSymbolAtLocation: (symbol, location) => {
|
|
location = getParseTreeNode(location);
|
|
return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType;
|
|
},
|
|
getSymbolsOfParameterPropertyDeclaration: (parameterIn, parameterName) => {
|
|
const parameter = getParseTreeNode(parameterIn, isParameter);
|
|
if (parameter === undefined) return Debug.fail("Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node.");
|
|
return getSymbolsOfParameterPropertyDeclaration(parameter, escapeLeadingUnderscores(parameterName));
|
|
},
|
|
getDeclaredTypeOfSymbol,
|
|
getPropertiesOfType,
|
|
getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)),
|
|
getPrivateIdentifierPropertyOfType: (leftType: Type, name: string, location: Node) => {
|
|
const node = getParseTreeNode(location);
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
const propName = escapeLeadingUnderscores(name);
|
|
const lexicallyScopedIdentifier = lookupSymbolForPrivateIdentifierDeclaration(propName, node);
|
|
return lexicallyScopedIdentifier ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedIdentifier) : undefined;
|
|
},
|
|
getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)),
|
|
getIndexInfoOfType,
|
|
getSignaturesOfType,
|
|
getIndexTypeOfType,
|
|
getBaseTypes,
|
|
getBaseTypeOfLiteralType,
|
|
getWidenedType,
|
|
getTypeFromTypeNode: nodeIn => {
|
|
const node = getParseTreeNode(nodeIn, isTypeNode);
|
|
return node ? getTypeFromTypeNode(node) : errorType;
|
|
},
|
|
getParameterType: getTypeAtPosition,
|
|
getPromisedTypeOfPromise,
|
|
getReturnTypeOfSignature,
|
|
isNullableType,
|
|
getNullableType,
|
|
getNonNullableType,
|
|
getNonOptionalType: removeOptionalTypeMarker,
|
|
getTypeArguments,
|
|
typeToTypeNode: nodeBuilder.typeToTypeNode,
|
|
indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration,
|
|
signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration,
|
|
symbolToEntityName: nodeBuilder.symbolToEntityName,
|
|
symbolToExpression: nodeBuilder.symbolToExpression,
|
|
symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations,
|
|
symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration,
|
|
typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration,
|
|
getSymbolsInScope: (location, meaning) => {
|
|
location = getParseTreeNode(location);
|
|
return location ? getSymbolsInScope(location, meaning) : [];
|
|
},
|
|
getSymbolAtLocation: node => {
|
|
node = getParseTreeNode(node);
|
|
// set ignoreErrors: true because any lookups invoked by the API shouldn't cause any new errors
|
|
return node ? getSymbolAtLocation(node, /*ignoreErrors*/ true) : undefined;
|
|
},
|
|
getShorthandAssignmentValueSymbol: node => {
|
|
node = getParseTreeNode(node);
|
|
return node ? getShorthandAssignmentValueSymbol(node) : undefined;
|
|
},
|
|
getExportSpecifierLocalTargetSymbol: nodeIn => {
|
|
const node = getParseTreeNode(nodeIn, isExportSpecifier);
|
|
return node ? getExportSpecifierLocalTargetSymbol(node) : undefined;
|
|
},
|
|
getExportSymbolOfSymbol(symbol) {
|
|
return getMergedSymbol(symbol.exportSymbol || symbol);
|
|
},
|
|
getTypeAtLocation: node => {
|
|
node = getParseTreeNode(node);
|
|
return node ? getTypeOfNode(node) : errorType;
|
|
},
|
|
getTypeOfAssignmentPattern: nodeIn => {
|
|
const node = getParseTreeNode(nodeIn, isAssignmentPattern);
|
|
return node && getTypeOfAssignmentPattern(node) || errorType;
|
|
},
|
|
getPropertySymbolOfDestructuringAssignment: locationIn => {
|
|
const location = getParseTreeNode(locationIn, isIdentifier);
|
|
return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined;
|
|
},
|
|
signatureToString: (signature, enclosingDeclaration, flags, kind) => {
|
|
return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind);
|
|
},
|
|
typeToString: (type, enclosingDeclaration, flags) => {
|
|
return typeToString(type, getParseTreeNode(enclosingDeclaration), flags);
|
|
},
|
|
symbolToString: (symbol, enclosingDeclaration, meaning, flags) => {
|
|
return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags);
|
|
},
|
|
typePredicateToString: (predicate, enclosingDeclaration, flags) => {
|
|
return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags);
|
|
},
|
|
writeSignature: (signature, enclosingDeclaration, flags, kind, writer) => {
|
|
return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer);
|
|
},
|
|
writeType: (type, enclosingDeclaration, flags, writer) => {
|
|
return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer);
|
|
},
|
|
writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => {
|
|
return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer);
|
|
},
|
|
writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => {
|
|
return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer);
|
|
},
|
|
getAugmentedPropertiesOfType,
|
|
getRootSymbols,
|
|
getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => {
|
|
const node = getParseTreeNode(nodeIn, isExpression);
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
const containingCall = findAncestor(node, isCallLikeExpression);
|
|
const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature;
|
|
if (contextFlags! & ContextFlags.Completions && containingCall) {
|
|
let toMarkSkip = node as Node;
|
|
do {
|
|
getNodeLinks(toMarkSkip).skipDirectInference = true;
|
|
toMarkSkip = toMarkSkip.parent;
|
|
} while (toMarkSkip && toMarkSkip !== containingCall);
|
|
getNodeLinks(containingCall).resolvedSignature = undefined;
|
|
}
|
|
const result = getContextualType(node, contextFlags);
|
|
if (contextFlags! & ContextFlags.Completions && containingCall) {
|
|
let toMarkSkip = node as Node;
|
|
do {
|
|
getNodeLinks(toMarkSkip).skipDirectInference = undefined;
|
|
toMarkSkip = toMarkSkip.parent;
|
|
} while (toMarkSkip && toMarkSkip !== containingCall);
|
|
getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature;
|
|
}
|
|
return result;
|
|
},
|
|
getContextualTypeForObjectLiteralElement: nodeIn => {
|
|
const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike);
|
|
return node ? getContextualTypeForObjectLiteralElement(node) : undefined;
|
|
},
|
|
getContextualTypeForArgumentAtIndex: (nodeIn, argIndex) => {
|
|
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
|
|
return node && getContextualTypeForArgumentAtIndex(node, argIndex);
|
|
},
|
|
getContextualTypeForJsxAttribute: (nodeIn) => {
|
|
const node = getParseTreeNode(nodeIn, isJsxAttributeLike);
|
|
return node && getContextualTypeForJsxAttribute(node);
|
|
},
|
|
isContextSensitive,
|
|
getFullyQualifiedName,
|
|
getResolvedSignature: (node, candidatesOutArray, argumentCount) =>
|
|
getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),
|
|
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) =>
|
|
getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp),
|
|
getExpandedParameters,
|
|
hasEffectiveRestParameter,
|
|
getConstantValue: nodeIn => {
|
|
const node = getParseTreeNode(nodeIn, canHaveConstantValue);
|
|
return node ? getConstantValue(node) : undefined;
|
|
},
|
|
isValidPropertyAccess: (nodeIn, propertyName) => {
|
|
const node = getParseTreeNode(nodeIn, isPropertyAccessOrQualifiedNameOrImportTypeNode);
|
|
return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName));
|
|
},
|
|
isValidPropertyAccessForCompletions: (nodeIn, type, property) => {
|
|
const node = getParseTreeNode(nodeIn, isPropertyAccessExpression);
|
|
return !!node && isValidPropertyAccessForCompletions(node, type, property);
|
|
},
|
|
getSignatureFromDeclaration: declarationIn => {
|
|
const declaration = getParseTreeNode(declarationIn, isFunctionLike);
|
|
return declaration ? getSignatureFromDeclaration(declaration) : undefined;
|
|
},
|
|
isImplementationOfOverload: node => {
|
|
const parsed = getParseTreeNode(node, isFunctionLike);
|
|
return parsed ? isImplementationOfOverload(parsed) : undefined;
|
|
},
|
|
getImmediateAliasedSymbol,
|
|
getAliasedSymbol: resolveAlias,
|
|
getEmitResolver,
|
|
getExportsOfModule: getExportsOfModuleAsArray,
|
|
getExportsAndPropertiesOfModule,
|
|
getSymbolWalker: createGetSymbolWalker(
|
|
getRestTypeOfSignature,
|
|
getTypePredicateOfSignature,
|
|
getReturnTypeOfSignature,
|
|
getBaseTypes,
|
|
resolveStructuredTypeMembers,
|
|
getTypeOfSymbol,
|
|
getResolvedSymbol,
|
|
getIndexTypeOfStructuredType,
|
|
getConstraintOfTypeParameter,
|
|
getFirstIdentifier,
|
|
getTypeArguments,
|
|
),
|
|
getAmbientModules,
|
|
getJsxIntrinsicTagNamesAt,
|
|
isOptionalParameter: nodeIn => {
|
|
const node = getParseTreeNode(nodeIn, isParameter);
|
|
return node ? isOptionalParameter(node) : false;
|
|
},
|
|
tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol),
|
|
tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol),
|
|
tryFindAmbientModuleWithoutAugmentations: moduleName => {
|
|
// we deliberately exclude augmentations
|
|
// since we are only interested in declarations of the module itself
|
|
return tryFindAmbientModule(moduleName, /*withAugmentations*/ false);
|
|
},
|
|
getApparentType,
|
|
getUnionType,
|
|
isTypeAssignableTo: (source, target) => {
|
|
return isTypeAssignableTo(source, target);
|
|
},
|
|
createAnonymousType,
|
|
createSignature,
|
|
createSymbol,
|
|
createIndexInfo,
|
|
getAnyType: () => anyType,
|
|
getStringType: () => stringType,
|
|
getNumberType: () => numberType,
|
|
createPromiseType,
|
|
createArrayType,
|
|
getElementTypeOfArrayType,
|
|
getBooleanType: () => booleanType,
|
|
getFalseType: (fresh?) => fresh ? falseType : regularFalseType,
|
|
getTrueType: (fresh?) => fresh ? trueType : regularTrueType,
|
|
getVoidType: () => voidType,
|
|
getUndefinedType: () => undefinedType,
|
|
getNullType: () => nullType,
|
|
getESSymbolType: () => esSymbolType,
|
|
getNeverType: () => neverType,
|
|
getOptionalType: () => optionalType,
|
|
isSymbolAccessible,
|
|
isArrayType,
|
|
isTupleType,
|
|
isArrayLikeType,
|
|
isTypeInvalidDueToUnionDiscriminant,
|
|
getAllPossiblePropertiesOfTypes,
|
|
getSuggestedSymbolForNonexistentProperty,
|
|
getSuggestionForNonexistentProperty,
|
|
getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
|
|
getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
|
|
getSuggestedSymbolForNonexistentModule,
|
|
getSuggestionForNonexistentExport,
|
|
getBaseConstraintOfType,
|
|
getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined,
|
|
resolveName(name, location, meaning, excludeGlobals) {
|
|
return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals);
|
|
},
|
|
getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)),
|
|
getAccessibleSymbolChain,
|
|
getTypePredicateOfSignature,
|
|
resolveExternalModuleName: moduleSpecifier => {
|
|
return resolveExternalModuleName(moduleSpecifier, moduleSpecifier, /*ignoreErrors*/ true);
|
|
},
|
|
resolveExternalModuleSymbol,
|
|
tryGetThisTypeAt: (node, includeGlobalThis) => {
|
|
node = getParseTreeNode(node);
|
|
return node && tryGetThisTypeAt(node, includeGlobalThis);
|
|
},
|
|
getTypeArgumentConstraint: nodeIn => {
|
|
const node = getParseTreeNode(nodeIn, isTypeNode);
|
|
return node && getTypeArgumentConstraint(node);
|
|
},
|
|
getSuggestionDiagnostics: (file, ct) => {
|
|
if (skipTypeChecking(file, compilerOptions, host)) {
|
|
return emptyArray;
|
|
}
|
|
|
|
let diagnostics: DiagnosticWithLocation[] | undefined;
|
|
try {
|
|
// Record the cancellation token so it can be checked later on during checkSourceElement.
|
|
// Do this in a finally block so we can ensure that it gets reset back to nothing after
|
|
// this call is done.
|
|
cancellationToken = ct;
|
|
|
|
// Ensure file is type checked
|
|
checkSourceFile(file);
|
|
Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked));
|
|
|
|
diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName));
|
|
checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => {
|
|
if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) {
|
|
(diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion });
|
|
}
|
|
});
|
|
|
|
return diagnostics || emptyArray;
|
|
}
|
|
finally {
|
|
cancellationToken = undefined;
|
|
}
|
|
},
|
|
|
|
runWithCancellationToken: (token, callback) => {
|
|
try {
|
|
cancellationToken = token;
|
|
return callback(checker);
|
|
}
|
|
finally {
|
|
cancellationToken = undefined;
|
|
}
|
|
},
|
|
|
|
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias,
|
|
isDeclarationVisible,
|
|
};
|
|
|
|
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {
|
|
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
|
|
apparentArgumentCount = argumentCount;
|
|
const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined;
|
|
apparentArgumentCount = undefined;
|
|
return res;
|
|
}
|
|
|
|
const tupleTypes = createMap<GenericType>();
|
|
const unionTypes = createMap<UnionType>();
|
|
const intersectionTypes = createMap<Type>();
|
|
const literalTypes = createMap<LiteralType>();
|
|
const indexedAccessTypes = createMap<IndexedAccessType>();
|
|
const substitutionTypes = createMap<SubstitutionType>();
|
|
const evolvingArrayTypes: EvolvingArrayType[] = [];
|
|
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
|
|
|
|
const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String);
|
|
const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving);
|
|
|
|
const anyType = createIntrinsicType(TypeFlags.Any, "any");
|
|
const autoType = createIntrinsicType(TypeFlags.Any, "any");
|
|
const wildcardType = createIntrinsicType(TypeFlags.Any, "any");
|
|
const errorType = createIntrinsicType(TypeFlags.Any, "error");
|
|
const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType);
|
|
const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown");
|
|
const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
|
|
const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType);
|
|
const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined");
|
|
const nullType = createIntrinsicType(TypeFlags.Null, "null");
|
|
const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType);
|
|
const stringType = createIntrinsicType(TypeFlags.String, "string");
|
|
const numberType = createIntrinsicType(TypeFlags.Number, "number");
|
|
const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint");
|
|
const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType;
|
|
const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType;
|
|
const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType;
|
|
const regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType;
|
|
trueType.regularType = regularTrueType;
|
|
trueType.freshType = trueType;
|
|
regularTrueType.regularType = regularTrueType;
|
|
regularTrueType.freshType = trueType;
|
|
falseType.regularType = regularFalseType;
|
|
falseType.freshType = falseType;
|
|
regularFalseType.regularType = regularFalseType;
|
|
regularFalseType.freshType = falseType;
|
|
const booleanType = createBooleanType([regularFalseType, regularTrueType]);
|
|
// Also mark all combinations of fresh/regular booleans as "Boolean" so they print as `boolean` instead of `true | false`
|
|
// (The union is cached, so simply doing the marking here is sufficient)
|
|
createBooleanType([regularFalseType, trueType]);
|
|
createBooleanType([falseType, regularTrueType]);
|
|
createBooleanType([falseType, trueType]);
|
|
const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol");
|
|
const voidType = createIntrinsicType(TypeFlags.Void, "void");
|
|
const neverType = createIntrinsicType(TypeFlags.Never, "never");
|
|
const silentNeverType = createIntrinsicType(TypeFlags.Never, "never");
|
|
const nonInferrableType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType);
|
|
const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never");
|
|
const unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never");
|
|
const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object");
|
|
const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]);
|
|
const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType;
|
|
const numberOrBigIntType = getUnionType([numberType, bigintType]);
|
|
|
|
const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(<TypeParameter>t) : t);
|
|
const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t);
|
|
|
|
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes;
|
|
|
|
const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type);
|
|
emptyTypeLiteralSymbol.members = createSymbolTable();
|
|
const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
|
|
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
emptyGenericType.instantiations = createMap<TypeReference>();
|
|
|
|
const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
// The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated
|
|
// in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes.
|
|
anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType;
|
|
|
|
const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
|
|
const markerSuperType = createTypeParameter();
|
|
const markerSubType = createTypeParameter();
|
|
markerSubType.constraint = markerSuperType;
|
|
const markerOtherType = createTypeParameter();
|
|
|
|
const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<<unresolved>>", 0, anyType);
|
|
|
|
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
|
|
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
|
|
const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
|
|
const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
|
|
|
|
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
|
|
|
|
const iterationTypesCache = createMap<IterationTypes>(); // cache for common IterationTypes instances
|
|
const noIterationTypes: IterationTypes = {
|
|
get yieldType(): Type { return Debug.fail("Not supported"); },
|
|
get returnType(): Type { return Debug.fail("Not supported"); },
|
|
get nextType(): Type { return Debug.fail("Not supported"); },
|
|
};
|
|
|
|
const anyIterationTypes = createIterationTypes(anyType, anyType, anyType);
|
|
const anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType);
|
|
const defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`.
|
|
|
|
const asyncIterationTypesResolver: IterationTypesResolver = {
|
|
iterableCacheKey: "iterationTypesOfAsyncIterable",
|
|
iteratorCacheKey: "iterationTypesOfAsyncIterator",
|
|
iteratorSymbolName: "asyncIterator",
|
|
getGlobalIteratorType: getGlobalAsyncIteratorType,
|
|
getGlobalIterableType: getGlobalAsyncIterableType,
|
|
getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType,
|
|
getGlobalGeneratorType: getGlobalAsyncGeneratorType,
|
|
resolveIterationType: getAwaitedType,
|
|
mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method,
|
|
mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method,
|
|
mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property,
|
|
};
|
|
|
|
const syncIterationTypesResolver: IterationTypesResolver = {
|
|
iterableCacheKey: "iterationTypesOfIterable",
|
|
iteratorCacheKey: "iterationTypesOfIterator",
|
|
iteratorSymbolName: "iterator",
|
|
getGlobalIteratorType,
|
|
getGlobalIterableType,
|
|
getGlobalIterableIteratorType,
|
|
getGlobalGeneratorType,
|
|
resolveIterationType: (type, _errorNode) => type,
|
|
mustHaveANextMethodDiagnostic: Diagnostics.An_iterator_must_have_a_next_method,
|
|
mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_iterator_must_be_a_method,
|
|
mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_iterator_must_have_a_value_property,
|
|
};
|
|
|
|
interface DuplicateInfoForSymbol {
|
|
readonly firstFileLocations: Node[];
|
|
readonly secondFileLocations: Node[];
|
|
readonly isBlockScoped: boolean;
|
|
}
|
|
interface DuplicateInfoForFiles {
|
|
readonly firstFile: SourceFile;
|
|
readonly secondFile: SourceFile;
|
|
/** Key is symbol name. */
|
|
readonly conflictingSymbols: Map<DuplicateInfoForSymbol>;
|
|
}
|
|
/** Key is "/path/to/a.ts|/path/to/b.ts". */
|
|
let amalgamatedDuplicates: Map<DuplicateInfoForFiles> | undefined;
|
|
const reverseMappedCache = createMap<Type | undefined>();
|
|
let ambientModulesCache: Symbol[] | undefined;
|
|
/**
|
|
* List of every ambient module with a "*" wildcard.
|
|
* Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches.
|
|
* This is only used if there is no exact match.
|
|
*/
|
|
let patternAmbientModules: PatternAmbientModule[];
|
|
let patternAmbientModuleAugmentations: Map<Symbol> | undefined;
|
|
|
|
let globalObjectType: ObjectType;
|
|
let globalFunctionType: ObjectType;
|
|
let globalCallableFunctionType: ObjectType;
|
|
let globalNewableFunctionType: ObjectType;
|
|
let globalArrayType: GenericType;
|
|
let globalReadonlyArrayType: GenericType;
|
|
let globalStringType: ObjectType;
|
|
let globalNumberType: ObjectType;
|
|
let globalBooleanType: ObjectType;
|
|
let globalRegExpType: ObjectType;
|
|
let globalThisType: GenericType;
|
|
let anyArrayType: Type;
|
|
let autoArrayType: Type;
|
|
let anyReadonlyArrayType: Type;
|
|
let deferredGlobalNonNullableTypeAlias: Symbol;
|
|
|
|
// The library files are only loaded when the feature is used.
|
|
// This allows users to just specify library files they want to used through --lib
|
|
// and they will not get an error from not having unrelated library files
|
|
let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined;
|
|
let deferredGlobalESSymbolType: ObjectType;
|
|
let deferredGlobalTypedPropertyDescriptorType: GenericType;
|
|
let deferredGlobalPromiseType: GenericType;
|
|
let deferredGlobalPromiseLikeType: GenericType;
|
|
let deferredGlobalPromiseConstructorSymbol: Symbol | undefined;
|
|
let deferredGlobalPromiseConstructorLikeType: ObjectType;
|
|
let deferredGlobalIterableType: GenericType;
|
|
let deferredGlobalIteratorType: GenericType;
|
|
let deferredGlobalIterableIteratorType: GenericType;
|
|
let deferredGlobalGeneratorType: GenericType;
|
|
let deferredGlobalIteratorYieldResultType: GenericType;
|
|
let deferredGlobalIteratorReturnResultType: GenericType;
|
|
let deferredGlobalAsyncIterableType: GenericType;
|
|
let deferredGlobalAsyncIteratorType: GenericType;
|
|
let deferredGlobalAsyncIterableIteratorType: GenericType;
|
|
let deferredGlobalAsyncGeneratorType: GenericType;
|
|
let deferredGlobalTemplateStringsArrayType: ObjectType;
|
|
let deferredGlobalImportMetaType: ObjectType;
|
|
let deferredGlobalExtractSymbol: Symbol;
|
|
let deferredGlobalOmitSymbol: Symbol;
|
|
let deferredGlobalBigIntType: ObjectType;
|
|
|
|
const allPotentiallyUnusedIdentifiers = createMap<PotentiallyUnusedIdentifier[]>(); // key is file name
|
|
|
|
let flowLoopStart = 0;
|
|
let flowLoopCount = 0;
|
|
let sharedFlowCount = 0;
|
|
let flowAnalysisDisabled = false;
|
|
let flowInvocationCount = 0;
|
|
let lastFlowNode: FlowNode | undefined;
|
|
let lastFlowNodeReachable: boolean;
|
|
let flowTypeCache: Type[] | undefined;
|
|
|
|
const emptyStringType = getLiteralType("");
|
|
const zeroType = getLiteralType(0);
|
|
const zeroBigIntType = getLiteralType({ negative: false, base10Value: "0" });
|
|
|
|
const resolutionTargets: TypeSystemEntity[] = [];
|
|
const resolutionResults: boolean[] = [];
|
|
const resolutionPropertyNames: TypeSystemPropertyName[] = [];
|
|
|
|
let suggestionCount = 0;
|
|
const maximumSuggestionCount = 10;
|
|
const mergedSymbols: Symbol[] = [];
|
|
const symbolLinks: SymbolLinks[] = [];
|
|
const nodeLinks: NodeLinks[] = [];
|
|
const flowLoopCaches: Map<Type>[] = [];
|
|
const flowLoopNodes: FlowNode[] = [];
|
|
const flowLoopKeys: string[] = [];
|
|
const flowLoopTypes: Type[][] = [];
|
|
const sharedFlowNodes: FlowNode[] = [];
|
|
const sharedFlowTypes: FlowType[] = [];
|
|
const flowNodeReachable: (boolean | undefined)[] = [];
|
|
const potentialThisCollisions: Node[] = [];
|
|
const potentialNewTargetCollisions: Node[] = [];
|
|
const potentialWeakMapCollisions: Node[] = [];
|
|
const awaitedTypeStack: number[] = [];
|
|
|
|
const diagnostics = createDiagnosticCollection();
|
|
const suggestionDiagnostics = createDiagnosticCollection();
|
|
|
|
const typeofTypesByName: ReadonlyMap<Type> = createMapFromTemplate<Type>({
|
|
string: stringType,
|
|
number: numberType,
|
|
bigint: bigintType,
|
|
boolean: booleanType,
|
|
symbol: esSymbolType,
|
|
undefined: undefinedType
|
|
});
|
|
const typeofType = createTypeofType();
|
|
|
|
let _jsxNamespace: __String;
|
|
let _jsxFactoryEntity: EntityName | undefined;
|
|
let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined;
|
|
|
|
const subtypeRelation = createMap<RelationComparisonResult>();
|
|
const strictSubtypeRelation = createMap<RelationComparisonResult>();
|
|
const assignableRelation = createMap<RelationComparisonResult>();
|
|
const comparableRelation = createMap<RelationComparisonResult>();
|
|
const identityRelation = createMap<RelationComparisonResult>();
|
|
const enumRelation = createMap<RelationComparisonResult>();
|
|
|
|
const builtinGlobals = createSymbolTable();
|
|
builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol);
|
|
|
|
initializeTypeChecker();
|
|
|
|
return checker;
|
|
|
|
function getJsxNamespace(location: Node | undefined): __String {
|
|
if (location) {
|
|
const file = getSourceFileOfNode(location);
|
|
if (file) {
|
|
if (file.localJsxNamespace) {
|
|
return file.localJsxNamespace;
|
|
}
|
|
const jsxPragma = file.pragmas.get("jsx");
|
|
if (jsxPragma) {
|
|
const chosenpragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma;
|
|
file.localJsxFactory = parseIsolatedEntityName(chosenpragma.arguments.factory, languageVersion);
|
|
visitNode(file.localJsxFactory, markAsSynthetic);
|
|
if (file.localJsxFactory) {
|
|
return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!_jsxNamespace) {
|
|
_jsxNamespace = "React" as __String;
|
|
if (compilerOptions.jsxFactory) {
|
|
_jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion);
|
|
visitNode(_jsxFactoryEntity, markAsSynthetic);
|
|
if (_jsxFactoryEntity) {
|
|
_jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText;
|
|
}
|
|
}
|
|
else if (compilerOptions.reactNamespace) {
|
|
_jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace);
|
|
}
|
|
}
|
|
if (!_jsxFactoryEntity) {
|
|
_jsxFactoryEntity = createQualifiedName(createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement");
|
|
}
|
|
return _jsxNamespace;
|
|
|
|
function markAsSynthetic(node: Node): VisitResult<Node> {
|
|
node.pos = -1;
|
|
node.end = -1;
|
|
return visitEachChild(node, markAsSynthetic, nullTransformationContext);
|
|
}
|
|
}
|
|
|
|
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) {
|
|
// Ensure we have all the type information in place for this file so that all the
|
|
// emitter questions of this resolver will return the right information.
|
|
getDiagnostics(sourceFile, cancellationToken);
|
|
return emitResolver;
|
|
}
|
|
|
|
function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
|
|
const diagnostic = location
|
|
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
|
|
: createCompilerDiagnostic(message, arg0, arg1, arg2, arg3);
|
|
const existing = diagnostics.lookup(diagnostic);
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
else {
|
|
diagnostics.add(diagnostic);
|
|
return diagnostic;
|
|
}
|
|
}
|
|
|
|
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
|
|
const diagnostic = location
|
|
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
|
|
: createCompilerDiagnostic(message, arg0, arg1, arg2, arg3);
|
|
diagnostics.add(diagnostic);
|
|
return diagnostic;
|
|
}
|
|
|
|
function addErrorOrSuggestion(isError: boolean, diagnostic: DiagnosticWithLocation) {
|
|
if (isError) {
|
|
diagnostics.add(diagnostic);
|
|
}
|
|
else {
|
|
suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion });
|
|
}
|
|
}
|
|
function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void {
|
|
addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line no-in-operator
|
|
}
|
|
|
|
function errorAndMaybeSuggestAwait(
|
|
location: Node,
|
|
maybeMissingAwait: boolean,
|
|
message: DiagnosticMessage,
|
|
arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic {
|
|
const diagnostic = error(location, message, arg0, arg1, arg2, arg3);
|
|
if (maybeMissingAwait) {
|
|
const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await);
|
|
addRelatedInfo(diagnostic, related);
|
|
}
|
|
return diagnostic;
|
|
}
|
|
|
|
function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) {
|
|
symbolCount++;
|
|
const symbol = <TransientSymbol>(new Symbol(flags | SymbolFlags.Transient, name));
|
|
symbol.checkFlags = checkFlags || 0;
|
|
return symbol;
|
|
}
|
|
|
|
function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags {
|
|
let result: SymbolFlags = 0;
|
|
if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes;
|
|
if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes;
|
|
if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes;
|
|
if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes;
|
|
if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes;
|
|
if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes;
|
|
if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes;
|
|
if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes;
|
|
if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes;
|
|
if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes;
|
|
if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes;
|
|
if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes;
|
|
if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes;
|
|
if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes;
|
|
if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes;
|
|
if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes;
|
|
return result;
|
|
}
|
|
|
|
function recordMergedSymbol(target: Symbol, source: Symbol) {
|
|
if (!source.mergeId) {
|
|
source.mergeId = nextMergeId;
|
|
nextMergeId++;
|
|
}
|
|
mergedSymbols[source.mergeId] = target;
|
|
}
|
|
|
|
function cloneSymbol(symbol: Symbol): Symbol {
|
|
const result = createSymbol(symbol.flags, symbol.escapedName);
|
|
result.declarations = symbol.declarations ? symbol.declarations.slice() : [];
|
|
result.parent = symbol.parent;
|
|
if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration;
|
|
if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true;
|
|
if (symbol.members) result.members = cloneMap(symbol.members);
|
|
if (symbol.exports) result.exports = cloneMap(symbol.exports);
|
|
recordMergedSymbol(result, symbol);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it.
|
|
* If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it.
|
|
*/
|
|
function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol {
|
|
if (!(target.flags & getExcludedSymbolFlags(source.flags)) ||
|
|
(source.flags | target.flags) & SymbolFlags.Assignment) {
|
|
if (source === target) {
|
|
// This can happen when an export assigned namespace exports something also erroneously exported at the top level
|
|
// See `declarationFileNoCrashOnExtraExportModifier` for an example
|
|
return target;
|
|
}
|
|
if (!(target.flags & SymbolFlags.Transient)) {
|
|
const resolvedTarget = resolveSymbol(target);
|
|
if (resolvedTarget === unknownSymbol) {
|
|
return source;
|
|
}
|
|
target = cloneSymbol(resolvedTarget);
|
|
}
|
|
// Javascript static-property-assignment declarations always merge, even though they are also values
|
|
if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) {
|
|
// reset flag when merging instantiated module into value module that has only const enums
|
|
target.constEnumOnlyModule = false;
|
|
}
|
|
target.flags |= source.flags;
|
|
if (source.valueDeclaration &&
|
|
(!target.valueDeclaration ||
|
|
isAssignmentDeclaration(target.valueDeclaration) && !isAssignmentDeclaration(source.valueDeclaration) ||
|
|
isEffectiveModuleDeclaration(target.valueDeclaration) && !isEffectiveModuleDeclaration(source.valueDeclaration))) {
|
|
// other kinds of value declarations take precedence over modules and assignment declarations
|
|
target.valueDeclaration = source.valueDeclaration;
|
|
}
|
|
addRange(target.declarations, source.declarations);
|
|
if (source.members) {
|
|
if (!target.members) target.members = createSymbolTable();
|
|
mergeSymbolTable(target.members, source.members, unidirectional);
|
|
}
|
|
if (source.exports) {
|
|
if (!target.exports) target.exports = createSymbolTable();
|
|
mergeSymbolTable(target.exports, source.exports, unidirectional);
|
|
}
|
|
if (!unidirectional) {
|
|
recordMergedSymbol(target, source);
|
|
}
|
|
}
|
|
else if (target.flags & SymbolFlags.NamespaceModule) {
|
|
// Do not report an error when merging `var globalThis` with the built-in `globalThis`,
|
|
// as we will already report a "Declaration name conflicts..." error, and this error
|
|
// won't make much sense.
|
|
if (target !== globalThisSymbol) {
|
|
error(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target));
|
|
}
|
|
}
|
|
else { // error
|
|
const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum);
|
|
const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable);
|
|
const message = isEitherEnum
|
|
? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations
|
|
: isEitherBlockScoped
|
|
? Diagnostics.Cannot_redeclare_block_scoped_variable_0
|
|
: Diagnostics.Duplicate_identifier_0;
|
|
const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]);
|
|
const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]);
|
|
const symbolName = symbolToString(source);
|
|
|
|
// Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch
|
|
if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) {
|
|
const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile;
|
|
const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile;
|
|
const filesDuplicates = getOrUpdate<DuplicateInfoForFiles>(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () =>
|
|
({ firstFile, secondFile, conflictingSymbols: createMap() }));
|
|
const conflictingSymbolInfo = getOrUpdate<DuplicateInfoForSymbol>(filesDuplicates.conflictingSymbols, symbolName, () =>
|
|
({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] }));
|
|
addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source);
|
|
addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target);
|
|
}
|
|
else {
|
|
addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target);
|
|
addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source);
|
|
}
|
|
}
|
|
return target;
|
|
|
|
function addDuplicateLocations(locs: Node[], symbol: Symbol): void {
|
|
for (const decl of symbol.declarations) {
|
|
pushIfUnique(locs, (getExpandoInitializer(decl, /*isPrototypeAssignment*/ false) ? getNameOfExpando(decl) : getNameOfDeclaration(decl)) || decl);
|
|
}
|
|
}
|
|
}
|
|
|
|
function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) {
|
|
forEach(target.declarations, node => {
|
|
const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node;
|
|
addDuplicateDeclarationError(errorNode, message, symbolName, source.declarations);
|
|
});
|
|
}
|
|
|
|
function addDuplicateDeclarationError(errorNode: Node, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Node[] | undefined) {
|
|
const err = lookupOrIssueError(errorNode, message, symbolName);
|
|
for (const relatedNode of relatedNodes || emptyArray) {
|
|
err.relatedInformation = err.relatedInformation || [];
|
|
if (length(err.relatedInformation) >= 5) continue;
|
|
addRelatedInfo(err, !length(err.relatedInformation) ? createDiagnosticForNode(relatedNode, Diagnostics._0_was_also_declared_here, symbolName) : createDiagnosticForNode(relatedNode, Diagnostics.and_here));
|
|
}
|
|
}
|
|
|
|
function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined {
|
|
if (!hasEntries(first)) return second;
|
|
if (!hasEntries(second)) return first;
|
|
const combined = createSymbolTable();
|
|
mergeSymbolTable(combined, first);
|
|
mergeSymbolTable(combined, second);
|
|
return combined;
|
|
}
|
|
|
|
function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) {
|
|
source.forEach((sourceSymbol, id) => {
|
|
const targetSymbol = target.get(id);
|
|
target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol);
|
|
});
|
|
}
|
|
|
|
function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void {
|
|
const moduleAugmentation = <ModuleDeclaration>moduleName.parent;
|
|
if (moduleAugmentation.symbol.declarations[0] !== moduleAugmentation) {
|
|
// this is a combined symbol for multiple augmentations within the same file.
|
|
// its symbol already has accumulated information for all declarations
|
|
// so we need to add it just once - do the work only for first declaration
|
|
Debug.assert(moduleAugmentation.symbol.declarations.length > 1);
|
|
return;
|
|
}
|
|
|
|
if (isGlobalScopeAugmentation(moduleAugmentation)) {
|
|
mergeSymbolTable(globals, moduleAugmentation.symbol.exports!);
|
|
}
|
|
else {
|
|
// find a module that about to be augmented
|
|
// do not validate names of augmentations that are defined in ambient context
|
|
const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient)
|
|
? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found
|
|
: undefined;
|
|
let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*isForAugmentation*/ true);
|
|
if (!mainModule) {
|
|
return;
|
|
}
|
|
// obtain item referenced by 'export='
|
|
mainModule = resolveExternalModuleSymbol(mainModule);
|
|
if (mainModule.flags & SymbolFlags.Namespace) {
|
|
// If we're merging an augmentation to a pattern ambient module, we want to
|
|
// perform the merge unidirectionally from the augmentation ('a.foo') to
|
|
// the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you
|
|
// all the exports both from the pattern and from the augmentation, but
|
|
// 'getMergedSymbol()' on *.foo only gives you exports from *.foo.
|
|
if (some(patternAmbientModules, module => mainModule === module.symbol)) {
|
|
const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true);
|
|
if (!patternAmbientModuleAugmentations) {
|
|
patternAmbientModuleAugmentations = createMap();
|
|
}
|
|
// moduleName will be a StringLiteral since this is not `declare global`.
|
|
patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged);
|
|
}
|
|
else {
|
|
mergeSymbol(mainModule, moduleAugmentation.symbol);
|
|
}
|
|
}
|
|
else {
|
|
// moduleName will be a StringLiteral since this is not `declare global`.
|
|
error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text);
|
|
}
|
|
}
|
|
}
|
|
|
|
function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) {
|
|
source.forEach((sourceSymbol, id) => {
|
|
const targetSymbol = target.get(id);
|
|
if (targetSymbol) {
|
|
// Error on redeclarations
|
|
forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message));
|
|
}
|
|
else {
|
|
target.set(id, sourceSymbol);
|
|
}
|
|
});
|
|
|
|
function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) {
|
|
return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id));
|
|
}
|
|
}
|
|
|
|
function getSymbolLinks(symbol: Symbol): SymbolLinks {
|
|
if (symbol.flags & SymbolFlags.Transient) return <TransientSymbol>symbol;
|
|
const id = getSymbolId(symbol);
|
|
return symbolLinks[id] || (symbolLinks[id] = new (<any>SymbolLinks)());
|
|
}
|
|
|
|
function getNodeLinks(node: Node): NodeLinks {
|
|
const nodeId = getNodeId(node);
|
|
return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (<any>NodeLinks)());
|
|
}
|
|
|
|
function isGlobalSourceFile(node: Node) {
|
|
return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(<SourceFile>node);
|
|
}
|
|
|
|
function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined {
|
|
if (meaning) {
|
|
const symbol = symbols.get(name);
|
|
if (symbol) {
|
|
Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here.");
|
|
if (symbol.flags & meaning) {
|
|
return symbol;
|
|
}
|
|
if (symbol.flags & SymbolFlags.Alias) {
|
|
const target = resolveAlias(symbol);
|
|
// Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors
|
|
if (target === unknownSymbol || target.flags & meaning) {
|
|
return symbol;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// return undefined if we can't find a symbol.
|
|
}
|
|
|
|
/**
|
|
* Get symbols that represent parameter-property-declaration as parameter and as property declaration
|
|
* @param parameter a parameterDeclaration node
|
|
* @param parameterName a name of the parameter to get the symbols for.
|
|
* @return a tuple of two symbols
|
|
*/
|
|
function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: __String): [Symbol, Symbol] {
|
|
const constructorDeclaration = parameter.parent;
|
|
const classDeclaration = parameter.parent.parent;
|
|
|
|
const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value);
|
|
const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value);
|
|
|
|
if (parameterSymbol && propertySymbol) {
|
|
return [parameterSymbol, propertySymbol];
|
|
}
|
|
|
|
return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration");
|
|
}
|
|
|
|
function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean {
|
|
const declarationFile = getSourceFileOfNode(declaration);
|
|
const useFile = getSourceFileOfNode(usage);
|
|
if (declarationFile !== useFile) {
|
|
if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) ||
|
|
(!compilerOptions.outFile && !compilerOptions.out) ||
|
|
isInTypeQuery(usage) ||
|
|
declaration.flags & NodeFlags.Ambient) {
|
|
// nodes are in different files and order cannot be determined
|
|
return true;
|
|
}
|
|
// declaration is after usage
|
|
// can be legal if usage is deferred (i.e. inside function or in initializer of instance property)
|
|
if (isUsedInFunctionOrInstanceProperty(usage, declaration)) {
|
|
return true;
|
|
}
|
|
const sourceFiles = host.getSourceFiles();
|
|
return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile);
|
|
}
|
|
|
|
if (declaration.pos <= usage.pos) {
|
|
// declaration is before usage
|
|
if (declaration.kind === SyntaxKind.BindingElement) {
|
|
// still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2])
|
|
const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement;
|
|
if (errorBindingElement) {
|
|
return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) ||
|
|
declaration.pos < errorBindingElement.pos;
|
|
}
|
|
// or it might be illegal if usage happens before parent variable is declared (eg var [a] = a)
|
|
return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage);
|
|
}
|
|
else if (declaration.kind === SyntaxKind.VariableDeclaration) {
|
|
// still might be illegal if usage is in the initializer of the variable declaration (eg var a = a)
|
|
return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage);
|
|
}
|
|
else if (isClassDeclaration(declaration)) {
|
|
// still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} })
|
|
return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration);
|
|
}
|
|
else if (isPropertyDeclaration(declaration)) {
|
|
// still might be illegal if a self-referencing property initializer (eg private x = this.x)
|
|
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false);
|
|
}
|
|
else if (isParameterPropertyDeclaration(declaration, declaration.parent)) {
|
|
const container = getEnclosingBlockScopeContainer(declaration.parent);
|
|
// foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property
|
|
return !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields
|
|
&& getContainingClass(declaration) === getContainingClass(usage)
|
|
&& isUsedInFunctionOrInstanceProperty(usage, declaration, container));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// declaration is after usage, but it can still be legal if usage is deferred:
|
|
// 1. inside an export specifier
|
|
// 2. inside a function
|
|
// 3. inside an instance property initializer, a reference to a non-instance property
|
|
// (except when target: "esnext" and useDefineForClassFields: true and the reference is to a parameter property)
|
|
// 4. inside a static property initializer, a reference to a static method in the same class
|
|
// 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ)
|
|
// or if usage is in a type context:
|
|
// 1. inside a type query (typeof in type position)
|
|
// 2. inside a jsdoc comment
|
|
if (usage.parent.kind === SyntaxKind.ExportSpecifier || (usage.parent.kind === SyntaxKind.ExportAssignment && (usage.parent as ExportAssignment).isExportEquals)) {
|
|
// export specifiers do not use the variable, they only make it available for use
|
|
return true;
|
|
}
|
|
// When resolving symbols for exports, the `usage` location passed in can be the export site directly
|
|
if (usage.kind === SyntaxKind.ExportAssignment && (usage as ExportAssignment).isExportEquals) {
|
|
return true;
|
|
}
|
|
|
|
const container = getEnclosingBlockScopeContainer(declaration);
|
|
if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage)) {
|
|
return true;
|
|
}
|
|
if (isUsedInFunctionOrInstanceProperty(usage, declaration, container)) {
|
|
if (compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields) {
|
|
return (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent)) &&
|
|
!isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true);
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean {
|
|
const container = getEnclosingBlockScopeContainer(declaration);
|
|
|
|
switch (declaration.parent.parent.kind) {
|
|
case SyntaxKind.VariableStatement:
|
|
case SyntaxKind.ForStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
// variable statement/for/for-of statement case,
|
|
// use site should not be inside variable declaration (initializer of declaration or binding element)
|
|
if (isSameScopeDescendentOf(usage, declaration, container)) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// ForIn/ForOf case - use site should not be used in expression part
|
|
const grandparent = declaration.parent.parent;
|
|
return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, container);
|
|
}
|
|
|
|
function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean {
|
|
return !!findAncestor(usage, current => {
|
|
if (current === container) {
|
|
return "quit";
|
|
}
|
|
if (isFunctionLike(current)) {
|
|
return true;
|
|
}
|
|
|
|
const initializerOfProperty = current.parent &&
|
|
current.parent.kind === SyntaxKind.PropertyDeclaration &&
|
|
(<PropertyDeclaration>current.parent).initializer === current;
|
|
|
|
if (initializerOfProperty) {
|
|
if (hasModifier(current.parent, ModifierFlags.Static)) {
|
|
if (declaration.kind === SyntaxKind.MethodDeclaration) {
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !hasModifier(declaration, ModifierFlags.Static);
|
|
if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
/** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */
|
|
function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) {
|
|
// always legal if usage is after declaration
|
|
if (usage.end > declaration.end) {
|
|
return false;
|
|
}
|
|
|
|
// still might be legal if usage is deferred (e.g. x: any = () => this.x)
|
|
// otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x)
|
|
const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => {
|
|
if (node === declaration) {
|
|
return "quit";
|
|
}
|
|
|
|
switch (node.kind) {
|
|
case SyntaxKind.ArrowFunction:
|
|
return true;
|
|
case SyntaxKind.PropertyDeclaration:
|
|
// even when stopping at any property declaration, they need to come from the same class
|
|
return stopAtAnyPropertyDeclaration &&
|
|
(isPropertyDeclaration(declaration) && node.parent === declaration.parent
|
|
|| isParameterPropertyDeclaration(declaration, declaration.parent) && node.parent === declaration.parent.parent)
|
|
? "quit": true;
|
|
case SyntaxKind.Block:
|
|
switch (node.parent.kind) {
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.SetAccessor:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return ancestorChangingReferenceScope === undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and
|
|
* the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with
|
|
* the given name can be found.
|
|
*
|
|
* @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters.
|
|
*/
|
|
function resolveName(
|
|
location: Node | undefined,
|
|
name: __String,
|
|
meaning: SymbolFlags,
|
|
nameNotFoundMessage: DiagnosticMessage | undefined,
|
|
nameArg: __String | Identifier | undefined,
|
|
isUse: boolean,
|
|
excludeGlobals = false,
|
|
suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol | undefined {
|
|
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol, suggestedNameNotFoundMessage);
|
|
}
|
|
|
|
function resolveNameHelper(
|
|
location: Node | undefined,
|
|
name: __String,
|
|
meaning: SymbolFlags,
|
|
nameNotFoundMessage: DiagnosticMessage | undefined,
|
|
nameArg: __String | Identifier | undefined,
|
|
isUse: boolean,
|
|
excludeGlobals: boolean,
|
|
lookup: typeof getSymbol,
|
|
suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol | undefined {
|
|
const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location
|
|
let result: Symbol | undefined;
|
|
let lastLocation: Node | undefined;
|
|
let lastSelfReferenceLocation: Node | undefined;
|
|
let propertyWithInvalidInitializer: Node | undefined;
|
|
let associatedDeclarationForContainingInitializer: ParameterDeclaration | BindingElement | undefined;
|
|
let withinDeferredContext = false;
|
|
const errorLocation = location;
|
|
let grandparent: Node;
|
|
let isInExternalModule = false;
|
|
|
|
loop: while (location) {
|
|
// Locals of a source file are not in scope (because they get merged into the global symbol table)
|
|
if (location.locals && !isGlobalSourceFile(location)) {
|
|
if (result = lookup(location.locals, name, meaning)) {
|
|
let useResult = true;
|
|
if (isFunctionLike(location) && lastLocation && lastLocation !== (<FunctionLikeDeclaration>location).body) {
|
|
// symbol lookup restrictions for function-like declarations
|
|
// - Type parameters of a function are in scope in the entire function declaration, including the parameter
|
|
// list and return type. However, local types are only in scope in the function body.
|
|
// - parameters are only in the scope of function body
|
|
// This restriction does not apply to JSDoc comment types because they are parented
|
|
// at a higher level than type parameters would normally be
|
|
if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDocComment) {
|
|
useResult = result.flags & SymbolFlags.TypeParameter
|
|
// type parameters are visible in parameter list, return type and type parameter list
|
|
? lastLocation === (<FunctionLikeDeclaration>location).type ||
|
|
lastLocation.kind === SyntaxKind.Parameter ||
|
|
lastLocation.kind === SyntaxKind.TypeParameter
|
|
// local types not visible outside the function body
|
|
: false;
|
|
}
|
|
if (meaning & result.flags & SymbolFlags.Variable) {
|
|
// expression inside parameter will lookup as normal variable scope when targeting es2015+
|
|
const functionLocation = <FunctionLikeDeclaration>location;
|
|
if (compilerOptions.target && compilerOptions.target >= ScriptTarget.ES2015 && isParameter(lastLocation) &&
|
|
functionLocation.body && result.valueDeclaration.pos >= functionLocation.body.pos && result.valueDeclaration.end <= functionLocation.body.end) {
|
|
useResult = false;
|
|
}
|
|
else if (result.flags & SymbolFlags.FunctionScopedVariable) {
|
|
// parameters are visible only inside function body, parameter list and return type
|
|
// technically for parameter list case here we might mix parameters and variables declared in function,
|
|
// however it is detected separately when checking initializers of parameters
|
|
// to make sure that they reference no variables declared after them.
|
|
useResult =
|
|
lastLocation.kind === SyntaxKind.Parameter ||
|
|
(
|
|
lastLocation === (<FunctionLikeDeclaration>location).type &&
|
|
!!findAncestor(result.valueDeclaration, isParameter)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else if (location.kind === SyntaxKind.ConditionalType) {
|
|
// A type parameter declared using 'infer T' in a conditional type is visible only in
|
|
// the true branch of the conditional type.
|
|
useResult = lastLocation === (<ConditionalTypeNode>location).trueType;
|
|
}
|
|
|
|
if (useResult) {
|
|
break loop;
|
|
}
|
|
else {
|
|
result = undefined;
|
|
}
|
|
}
|
|
}
|
|
withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation);
|
|
switch (location.kind) {
|
|
case SyntaxKind.SourceFile:
|
|
if (!isExternalOrCommonJsModule(<SourceFile>location)) break;
|
|
isInExternalModule = true;
|
|
// falls through
|
|
case SyntaxKind.ModuleDeclaration:
|
|
const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration).exports || emptySymbols;
|
|
if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) {
|
|
|
|
// It's an external module. First see if the module has an export default and if the local
|
|
// name of that export default matches.
|
|
if (result = moduleExports.get(InternalSymbolName.Default)) {
|
|
const localSymbol = getLocalSymbolForExportDefault(result);
|
|
if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) {
|
|
break loop;
|
|
}
|
|
result = undefined;
|
|
}
|
|
|
|
// Because of module/namespace merging, a module's exports are in scope,
|
|
// yet we never want to treat an export specifier as putting a member in scope.
|
|
// Therefore, if the name we find is purely an export specifier, it is not actually considered in scope.
|
|
// Two things to note about this:
|
|
// 1. We have to check this without calling getSymbol. The problem with calling getSymbol
|
|
// on an export specifier is that it might find the export specifier itself, and try to
|
|
// resolve it as an alias. This will cause the checker to consider the export specifier
|
|
// a circular alias reference when it might not be.
|
|
// 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely*
|
|
// an alias. If we used &, we'd be throwing out symbols that have non alias aspects,
|
|
// which is not the desired behavior.
|
|
const moduleExport = moduleExports.get(name);
|
|
if (moduleExport &&
|
|
moduleExport.flags === SymbolFlags.Alias &&
|
|
(getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier) || getDeclarationOfKind(moduleExport, SyntaxKind.NamespaceExport))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs)
|
|
if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) {
|
|
if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations.some(isJSDocTypeAlias)) {
|
|
result = undefined;
|
|
}
|
|
else {
|
|
break loop;
|
|
}
|
|
}
|
|
break;
|
|
case SyntaxKind.EnumDeclaration:
|
|
if (result = lookup(getSymbolOfNode(location)!.exports!, name, meaning & SymbolFlags.EnumMember)) {
|
|
break loop;
|
|
}
|
|
break;
|
|
case SyntaxKind.PropertyDeclaration:
|
|
// TypeScript 1.0 spec (April 2014): 8.4.1
|
|
// Initializer expressions for instance member variables are evaluated in the scope
|
|
// of the class constructor body but are not permitted to reference parameters or
|
|
// local variables of the constructor. This effectively means that entities from outer scopes
|
|
// by the same name as a constructor parameter or local variable are inaccessible
|
|
// in initializer expressions for instance member variables.
|
|
if (!hasModifier(location, ModifierFlags.Static)) {
|
|
const ctor = findConstructorDeclaration(location.parent as ClassLikeDeclaration);
|
|
if (ctor && ctor.locals) {
|
|
if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) {
|
|
// Remember the property node, it will be used later to report appropriate error
|
|
propertyWithInvalidInitializer = location;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
// The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals
|
|
// These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would
|
|
// trigger resolving late-bound names, which we may already be in the process of doing while we're here!
|
|
if (result = lookup(getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols, name, meaning & SymbolFlags.Type)) {
|
|
if (!isTypeParameterSymbolDeclaredInContainer(result, location)) {
|
|
// ignore type parameters not declared in this container
|
|
result = undefined;
|
|
break;
|
|
}
|
|
if (lastLocation && hasModifier(lastLocation, ModifierFlags.Static)) {
|
|
// TypeScript 1.0 spec (April 2014): 3.4.1
|
|
// The scope of a type parameter extends over the entire declaration with which the type
|
|
// parameter list is associated, with the exception of static member declarations in classes.
|
|
error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters);
|
|
return undefined;
|
|
}
|
|
break loop;
|
|
}
|
|
if (location.kind === SyntaxKind.ClassExpression && meaning & SymbolFlags.Class) {
|
|
const className = (<ClassExpression>location).name;
|
|
if (className && name === className.escapedText) {
|
|
result = location.symbol;
|
|
break loop;
|
|
}
|
|
}
|
|
break;
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
// The type parameters of a class are not in scope in the base class expression.
|
|
if (lastLocation === (<ExpressionWithTypeArguments>location).expression && (<HeritageClause>location.parent).token === SyntaxKind.ExtendsKeyword) {
|
|
const container = location.parent.parent;
|
|
if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members!, name, meaning & SymbolFlags.Type))) {
|
|
if (nameNotFoundMessage) {
|
|
error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
break;
|
|
// It is not legal to reference a class's own type parameters from a computed property name that
|
|
// belongs to the class. For example:
|
|
//
|
|
// function foo<T>() { return '' }
|
|
// class C<T> { // <-- Class's own type parameter T
|
|
// [foo<T>()]() { } // <-- Reference to T from class's own computed property
|
|
// }
|
|
//
|
|
case SyntaxKind.ComputedPropertyName:
|
|
grandparent = location.parent.parent;
|
|
if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) {
|
|
// A reference to this grandparent's type parameters would be an error
|
|
if (result = lookup(getSymbolOfNode(grandparent as ClassLikeDeclaration | InterfaceDeclaration).members!, name, meaning & SymbolFlags.Type)) {
|
|
error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type);
|
|
return undefined;
|
|
}
|
|
}
|
|
break;
|
|
case SyntaxKind.ArrowFunction:
|
|
// when targeting ES6 or higher there is no 'arguments' in an arrow function
|
|
// for lower compile targets the resolved symbol is used to emit an error
|
|
if (compilerOptions.target! >= ScriptTarget.ES2015) {
|
|
break;
|
|
}
|
|
// falls through
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
if (meaning & SymbolFlags.Variable && name === "arguments") {
|
|
result = argumentsSymbol;
|
|
break loop;
|
|
}
|
|
break;
|
|
case SyntaxKind.FunctionExpression:
|
|
if (meaning & SymbolFlags.Variable && name === "arguments") {
|
|
result = argumentsSymbol;
|
|
break loop;
|
|
}
|
|
|
|
if (meaning & SymbolFlags.Function) {
|
|
const functionName = (<FunctionExpression>location).name;
|
|
if (functionName && name === functionName.escapedText) {
|
|
result = location.symbol;
|
|
break loop;
|
|
}
|
|
}
|
|
break;
|
|
case SyntaxKind.Decorator:
|
|
// Decorators are resolved at the class declaration. Resolving at the parameter
|
|
// or member would result in looking up locals in the method.
|
|
//
|
|
// function y() {}
|
|
// class C {
|
|
// method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter.
|
|
// }
|
|
//
|
|
if (location.parent && location.parent.kind === SyntaxKind.Parameter) {
|
|
location = location.parent;
|
|
}
|
|
//
|
|
// function y() {}
|
|
// class C {
|
|
// @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method.
|
|
// }
|
|
//
|
|
|
|
// class Decorators are resolved outside of the class to avoid referencing type parameters of that class.
|
|
//
|
|
// type T = number;
|
|
// declare function y(x: T): any;
|
|
// @param(1 as T) // <-- T should resolve to the type alias outside of class C
|
|
// class C<T> {}
|
|
if (location.parent && (isClassElement(location.parent) || location.parent.kind === SyntaxKind.ClassDeclaration)) {
|
|
location = location.parent;
|
|
}
|
|
break;
|
|
case SyntaxKind.JSDocTypedefTag:
|
|
case SyntaxKind.JSDocCallbackTag:
|
|
case SyntaxKind.JSDocEnumTag:
|
|
// js type aliases do not resolve names from their host, so skip past it
|
|
location = getJSDocHost(location);
|
|
break;
|
|
case SyntaxKind.Parameter:
|
|
if (lastLocation && lastLocation === (location as ParameterDeclaration).initializer) {
|
|
associatedDeclarationForContainingInitializer = location as ParameterDeclaration;
|
|
}
|
|
break;
|
|
case SyntaxKind.BindingElement:
|
|
if (lastLocation && lastLocation === (location as BindingElement).initializer) {
|
|
const root = getRootDeclaration(location);
|
|
if (root.kind === SyntaxKind.Parameter) {
|
|
associatedDeclarationForContainingInitializer = location as BindingElement;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (isSelfReferenceLocation(location)) {
|
|
lastSelfReferenceLocation = location;
|
|
}
|
|
lastLocation = location;
|
|
location = location.parent;
|
|
}
|
|
|
|
// We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`.
|
|
// If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself.
|
|
// That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used.
|
|
if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) {
|
|
result.isReferenced! |= meaning;
|
|
}
|
|
|
|
if (!result) {
|
|
if (lastLocation) {
|
|
Debug.assert(lastLocation.kind === SyntaxKind.SourceFile);
|
|
if ((lastLocation as SourceFile).commonJsModuleIndicator && name === "exports" && meaning & lastLocation.symbol.flags) {
|
|
return lastLocation.symbol;
|
|
}
|
|
}
|
|
|
|
if (!excludeGlobals) {
|
|
result = lookup(globals, name, meaning);
|
|
}
|
|
}
|
|
if (!result) {
|
|
if (originalLocation && isInJSFile(originalLocation) && originalLocation.parent) {
|
|
if (isRequireCall(originalLocation.parent, /*checkArgumentIsStringLiteralLike*/ false)) {
|
|
return requireSymbol;
|
|
}
|
|
}
|
|
}
|
|
if (!result) {
|
|
if (nameNotFoundMessage) {
|
|
if (!errorLocation ||
|
|
!checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217
|
|
!checkAndReportErrorForExtendingInterface(errorLocation) &&
|
|
!checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) &&
|
|
!checkAndReportErrorForExportingPrimitiveType(errorLocation, name) &&
|
|
!checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) &&
|
|
!checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) &&
|
|
!checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) {
|
|
let suggestion: Symbol | undefined;
|
|
if (suggestedNameNotFoundMessage && suggestionCount < maximumSuggestionCount) {
|
|
suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning);
|
|
if (suggestion) {
|
|
const suggestionName = symbolToString(suggestion);
|
|
const diagnostic = error(errorLocation, suggestedNameNotFoundMessage, diagnosticName(nameArg!), suggestionName);
|
|
if (suggestion.valueDeclaration) {
|
|
addRelatedInfo(
|
|
diagnostic,
|
|
createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (!suggestion) {
|
|
error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg!));
|
|
}
|
|
suggestionCount++;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Perform extra checks only if error reporting was requested
|
|
if (nameNotFoundMessage) {
|
|
if (propertyWithInvalidInitializer && !(compilerOptions.target === ScriptTarget.ESNext && compilerOptions.useDefineForClassFields)) {
|
|
// We have a match, but the reference occurred within a property initializer and the identifier also binds
|
|
// to a local variable in the constructor where the code will be emitted. Note that this is actually allowed
|
|
// with ESNext+useDefineForClassFields because the scope semantics are different.
|
|
const propertyName = (<PropertyDeclaration>propertyWithInvalidInitializer).name;
|
|
error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor,
|
|
declarationNameToString(propertyName), diagnosticName(nameArg!));
|
|
return undefined;
|
|
}
|
|
|
|
// Only check for block-scoped variable if we have an error location and are looking for the
|
|
// name with variable meaning
|
|
// For example,
|
|
// declare module foo {
|
|
// interface bar {}
|
|
// }
|
|
// const foo/*1*/: foo/*2*/.bar;
|
|
// The foo at /*1*/ and /*2*/ will share same symbol with two meanings:
|
|
// block-scoped variable and namespace module. However, only when we
|
|
// try to resolve name in /*1*/ which is used in variable position,
|
|
// we want to check for block-scoped
|
|
if (errorLocation &&
|
|
(meaning & SymbolFlags.BlockScopedVariable ||
|
|
((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) {
|
|
const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result);
|
|
if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) {
|
|
checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation);
|
|
}
|
|
}
|
|
|
|
// If we're in an external module, we can't reference value symbols created from UMD export declarations
|
|
if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) {
|
|
const merged = getMergedSymbol(result);
|
|
if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) {
|
|
errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name));
|
|
}
|
|
}
|
|
|
|
// If we're in a parameter initializer, we can't reference the values of the parameter whose initializer we're within or parameters to the right
|
|
if (result && associatedDeclarationForContainingInitializer && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) {
|
|
const candidate = getMergedSymbol(getLateBoundSymbol(result));
|
|
const root = (getRootDeclaration(associatedDeclarationForContainingInitializer) as ParameterDeclaration);
|
|
// A parameter initializer or binding pattern initializer within a parameter cannot refer to itself
|
|
if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializer)) {
|
|
error(errorLocation, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(associatedDeclarationForContainingInitializer.name));
|
|
}
|
|
// And it cannot refer to any declarations which come after it
|
|
else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializer.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) {
|
|
error(errorLocation, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializer.name), declarationNameToString(<Identifier>errorLocation));
|
|
}
|
|
}
|
|
if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias) {
|
|
checkSymbolUsageInExpressionContext(result, name, errorLocation);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function checkSymbolUsageInExpressionContext(symbol: Symbol, name: __String, useSite: Node) {
|
|
if (!isValidTypeOnlyAliasUseSite(useSite)) {
|
|
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol);
|
|
if (typeOnlyDeclaration) {
|
|
const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration);
|
|
const message = isExport
|
|
? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type
|
|
: Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type;
|
|
const relatedMessage = isExport
|
|
? Diagnostics._0_was_exported_here
|
|
: Diagnostics._0_was_imported_here;
|
|
const unescapedName = unescapeLeadingUnderscores(name);
|
|
addRelatedInfo(
|
|
error(useSite, message, unescapedName),
|
|
createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, unescapedName));
|
|
}
|
|
}
|
|
}
|
|
|
|
function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean {
|
|
if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) {
|
|
// initializers in instance property declaration of class like entities are executed in constructor and thus deferred
|
|
return isTypeQueryNode(location) || ((
|
|
isFunctionLikeDeclaration(location) ||
|
|
(location.kind === SyntaxKind.PropertyDeclaration && !hasModifier(location, ModifierFlags.Static))
|
|
) && (!lastLocation || lastLocation !== (location as FunctionLike | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred
|
|
}
|
|
if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) {
|
|
return false;
|
|
}
|
|
// generator functions and async functions are not inlined in control flow when immediately invoked
|
|
if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasModifier(location, ModifierFlags.Async)) {
|
|
return true;
|
|
}
|
|
return !getImmediatelyInvokedFunctionExpression(location);
|
|
}
|
|
|
|
function isSelfReferenceLocation(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }`
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) {
|
|
return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier);
|
|
}
|
|
|
|
function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) {
|
|
for (const decl of symbol.declarations) {
|
|
if (decl.kind === SyntaxKind.TypeParameter) {
|
|
const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent;
|
|
if (parent === container) {
|
|
return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags!, isJSDocTypeAlias)); // TODO: GH#18217
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function checkAndReportErrorForMissingPrefix(errorLocation: Node, name: __String, nameArg: __String | Identifier): boolean {
|
|
if (!isIdentifier(errorLocation) || errorLocation.escapedText !== name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation)) {
|
|
return false;
|
|
}
|
|
|
|
const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ false);
|
|
let location = container;
|
|
while (location) {
|
|
if (isClassLike(location.parent)) {
|
|
const classSymbol = getSymbolOfNode(location.parent);
|
|
if (!classSymbol) {
|
|
break;
|
|
}
|
|
|
|
// Check to see if a static member exists.
|
|
const constructorType = getTypeOfSymbol(classSymbol);
|
|
if (getPropertyOfType(constructorType, name)) {
|
|
error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0, diagnosticName(nameArg), symbolToString(classSymbol));
|
|
return true;
|
|
}
|
|
|
|
// No static member is present.
|
|
// Check if we're in an instance method and look for a relevant instance member.
|
|
if (location === container && !hasModifier(location, ModifierFlags.Static)) {
|
|
const instanceType = (<InterfaceType>getDeclaredTypeOfSymbol(classSymbol)).thisType!; // TODO: GH#18217
|
|
if (getPropertyOfType(instanceType, name)) {
|
|
error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, diagnosticName(nameArg));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
location = location.parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean {
|
|
const expression = getEntityNameForExtendingInterface(errorLocation);
|
|
if (expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) {
|
|
error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Climbs up parents to an ExpressionWithTypeArguments, and returns its expression,
|
|
* but returns undefined if that expression is not an EntityNameExpression.
|
|
*/
|
|
function getEntityNameForExtendingInterface(node: Node): EntityNameExpression | undefined {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
return node.parent ? getEntityNameForExtendingInterface(node.parent) : undefined;
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
if (isEntityNameExpression((<ExpressionWithTypeArguments>node).expression)) {
|
|
return <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression;
|
|
}
|
|
// falls through
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean {
|
|
const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(errorLocation) ? SymbolFlags.Value : 0);
|
|
if (meaning === namespaceMeaning) {
|
|
const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false));
|
|
const parent = errorLocation.parent;
|
|
if (symbol) {
|
|
if (isQualifiedName(parent)) {
|
|
Debug.assert(parent.left === errorLocation, "Should only be resolving left side of qualified name as a namespace");
|
|
const propName = parent.right.escapedText;
|
|
const propType = getPropertyOfType(getDeclaredTypeOfSymbol(symbol), propName);
|
|
if (propType) {
|
|
error(
|
|
parent,
|
|
Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1,
|
|
unescapeLeadingUnderscores(name),
|
|
unescapeLeadingUnderscores(propName),
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_namespace_here, unescapeLeadingUnderscores(name));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function checkAndReportErrorForUsingValueAsType(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean {
|
|
if (meaning & (SymbolFlags.Type & ~SymbolFlags.Namespace)) {
|
|
const symbol = resolveSymbol(resolveName(errorLocation, name, ~SymbolFlags.Type & SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false));
|
|
if (symbol && !(symbol.flags & SymbolFlags.Namespace)) {
|
|
error(errorLocation, Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, unescapeLeadingUnderscores(name));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isPrimitiveTypeName(name: __String) {
|
|
return name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never" || name === "unknown";
|
|
}
|
|
|
|
function checkAndReportErrorForExportingPrimitiveType(errorLocation: Node, name: __String): boolean {
|
|
if (isPrimitiveTypeName(name) && errorLocation.parent.kind === SyntaxKind.ExportSpecifier) {
|
|
error(errorLocation, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, name as string);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean {
|
|
if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule)) {
|
|
if (isPrimitiveTypeName(name)) {
|
|
error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name));
|
|
return true;
|
|
}
|
|
const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false));
|
|
if (symbol && !(symbol.flags & SymbolFlags.NamespaceModule)) {
|
|
const message = isES2015OrLaterConstructorName(name)
|
|
? Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later
|
|
: Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here;
|
|
error(errorLocation, message, unescapeLeadingUnderscores(name));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isES2015OrLaterConstructorName(n: __String) {
|
|
switch (n) {
|
|
case "Promise":
|
|
case "Symbol":
|
|
case "Map":
|
|
case "WeakMap":
|
|
case "Set":
|
|
case "WeakSet":
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean {
|
|
if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Type)) {
|
|
const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false));
|
|
if (symbol) {
|
|
error(
|
|
errorLocation,
|
|
Diagnostics.Cannot_use_namespace_0_as_a_value,
|
|
unescapeLeadingUnderscores(name));
|
|
return true;
|
|
}
|
|
}
|
|
else if (meaning & (SymbolFlags.Type & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Value)) {
|
|
const symbol = resolveSymbol(resolveName(errorLocation, name, (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) & ~SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false));
|
|
if (symbol) {
|
|
error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void {
|
|
Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum));
|
|
if (result.flags & (SymbolFlags.Function | SymbolFlags.FunctionScopedVariable | SymbolFlags.Assignment) && result.flags & SymbolFlags.Class) {
|
|
// constructor functions aren't block scoped
|
|
return;
|
|
}
|
|
// Block-scoped variables cannot be used before their definition
|
|
const declaration = find(
|
|
result.declarations,
|
|
d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration));
|
|
|
|
if (declaration === undefined) return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration");
|
|
|
|
if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) {
|
|
let diagnosticMessage;
|
|
const declarationName = declarationNameToString(getNameOfDeclaration(declaration));
|
|
if (result.flags & SymbolFlags.BlockScopedVariable) {
|
|
diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName);
|
|
}
|
|
else if (result.flags & SymbolFlags.Class) {
|
|
diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName);
|
|
}
|
|
else if (result.flags & SymbolFlags.RegularEnum) {
|
|
diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName);
|
|
}
|
|
else {
|
|
Debug.assert(!!(result.flags & SymbolFlags.ConstEnum));
|
|
if (compilerOptions.preserveConstEnums) {
|
|
diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName);
|
|
}
|
|
}
|
|
|
|
if (diagnosticMessage) {
|
|
addRelatedInfo(diagnosticMessage,
|
|
createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached.
|
|
* If at any point current node is equal to 'parent' node - return true.
|
|
* Return false if 'stopAt' node is reached or isFunctionLike(current) === true.
|
|
*/
|
|
function isSameScopeDescendentOf(initial: Node, parent: Node | undefined, stopAt: Node): boolean {
|
|
return !!parent && !!findAncestor(initial, n => n === stopAt || isFunctionLike(n) ? "quit" : n === parent);
|
|
}
|
|
|
|
function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
return node as ImportEqualsDeclaration;
|
|
case SyntaxKind.ImportClause:
|
|
return (node as ImportClause).parent;
|
|
case SyntaxKind.NamespaceImport:
|
|
return (node as NamespaceImport).parent.parent;
|
|
case SyntaxKind.ImportSpecifier:
|
|
return (node as ImportSpecifier).parent.parent.parent;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration | undefined {
|
|
return find<Declaration>(symbol.declarations, isAliasSymbolDeclaration);
|
|
}
|
|
|
|
/**
|
|
* An alias symbol is created by one of the following declarations:
|
|
* import <symbol> = ...
|
|
* import <symbol> from ...
|
|
* import * as <symbol> from ...
|
|
* import { x as <symbol> } from ...
|
|
* export { x as <symbol> } from ...
|
|
* export * as ns <symbol> from ...
|
|
* export = <EntityNameExpression>
|
|
* export default <EntityNameExpression>
|
|
* module.exports = <EntityNameExpression>
|
|
* {<Identifier>}
|
|
* {name: <EntityNameExpression>}
|
|
*/
|
|
function isAliasSymbolDeclaration(node: Node): boolean {
|
|
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
|
|
node.kind === SyntaxKind.NamespaceExportDeclaration ||
|
|
node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name ||
|
|
node.kind === SyntaxKind.NamespaceImport ||
|
|
node.kind === SyntaxKind.NamespaceExport ||
|
|
node.kind === SyntaxKind.ImportSpecifier ||
|
|
node.kind === SyntaxKind.ExportSpecifier ||
|
|
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node) ||
|
|
isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) ||
|
|
isPropertyAccessExpression(node)
|
|
&& isBinaryExpression(node.parent)
|
|
&& node.parent.left === node
|
|
&& node.parent.operatorToken.kind === SyntaxKind.EqualsToken
|
|
&& isAliasableOrJsExpression(node.parent.right) ||
|
|
node.kind === SyntaxKind.ShorthandPropertyAssignment ||
|
|
node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer);
|
|
}
|
|
|
|
function isAliasableOrJsExpression(e: Expression) {
|
|
return isAliasableExpression(e) || isFunctionExpression(e) && isJSConstructor(e);
|
|
}
|
|
|
|
function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration, dontResolveAlias: boolean): Symbol | undefined {
|
|
if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) {
|
|
const immediate = resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node));
|
|
const resolved = resolveExternalModuleSymbol(immediate);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias);
|
|
checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved);
|
|
return resolved;
|
|
}
|
|
|
|
function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) {
|
|
if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false)) {
|
|
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!;
|
|
const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration);
|
|
const message = isExport
|
|
? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type
|
|
: Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type;
|
|
const relatedMessage = isExport
|
|
? Diagnostics._0_was_exported_here
|
|
: Diagnostics._0_was_imported_here;
|
|
|
|
// Non-null assertion is safe because the optionality comes from ImportClause,
|
|
// but if an ImportClause was the typeOnlyDeclaration, it had to have a `name`.
|
|
const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name!.escapedText);
|
|
addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name));
|
|
}
|
|
}
|
|
|
|
function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) {
|
|
const exportValue = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals);
|
|
if (exportValue) {
|
|
return getPropertyOfType(getTypeOfSymbol(exportValue), name);
|
|
}
|
|
const exportSymbol = moduleSymbol.exports!.get(name);
|
|
const resolved = resolveSymbol(exportSymbol, dontResolveAlias);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
|
|
function isSyntacticDefault(node: Node) {
|
|
return ((isExportAssignment(node) && !node.isExportEquals) || hasModifier(node, ModifierFlags.Default) || isExportSpecifier(node));
|
|
}
|
|
|
|
function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean) {
|
|
if (!allowSyntheticDefaultImports) {
|
|
return false;
|
|
}
|
|
// Declaration files (and ambient modules)
|
|
if (!file || file.isDeclarationFile) {
|
|
// Definitely cannot have a synthetic default if they have a syntactic default member specified
|
|
const defaultExportSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, /*sourceNode*/ undefined, /*dontResolveAlias*/ true); // Dont resolve alias because we want the immediately exported symbol's declaration
|
|
if (defaultExportSymbol && some(defaultExportSymbol.declarations, isSyntacticDefault)) {
|
|
return false;
|
|
}
|
|
// It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member
|
|
// So we check a bit more,
|
|
if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias)) {
|
|
// If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code),
|
|
// it definitely is a module and does not have a synthetic default
|
|
return false;
|
|
}
|
|
// There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set
|
|
// Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member
|
|
// as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm
|
|
return true;
|
|
}
|
|
// TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement
|
|
if (!isSourceFileJS(file)) {
|
|
return hasExportAssignmentSymbol(moduleSymbol);
|
|
}
|
|
// JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker
|
|
return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias);
|
|
}
|
|
|
|
function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined {
|
|
const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier);
|
|
if (moduleSymbol) {
|
|
let exportDefaultSymbol: Symbol | undefined;
|
|
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
|
|
exportDefaultSymbol = moduleSymbol;
|
|
}
|
|
else {
|
|
exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias);
|
|
}
|
|
|
|
const file = find(moduleSymbol.declarations, isSourceFile);
|
|
const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias);
|
|
if (!exportDefaultSymbol && !hasSyntheticDefault) {
|
|
if (hasExportAssignmentSymbol(moduleSymbol)) {
|
|
const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop";
|
|
const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals);
|
|
const exportAssignment = exportEqualsSymbol!.valueDeclaration;
|
|
const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName);
|
|
|
|
addRelatedInfo(err, createDiagnosticForNode(
|
|
exportAssignment,
|
|
Diagnostics.This_module_is_declared_with_using_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag,
|
|
compilerOptionName
|
|
));
|
|
}
|
|
else {
|
|
if (moduleSymbol.exports && moduleSymbol.exports.has(node.symbol.escapedName)) {
|
|
error(
|
|
node.name,
|
|
Diagnostics.Module_0_has_no_default_export_Did_you_mean_to_use_import_1_from_0_instead,
|
|
symbolToString(moduleSymbol),
|
|
symbolToString(node.symbol),
|
|
);
|
|
}
|
|
else {
|
|
error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol));
|
|
}
|
|
|
|
}
|
|
}
|
|
else if (hasSyntheticDefault) {
|
|
// per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present
|
|
const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false);
|
|
return resolved;
|
|
}
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false);
|
|
return exportDefaultSymbol;
|
|
}
|
|
}
|
|
|
|
function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined {
|
|
const moduleSpecifier = node.parent.parent.moduleSpecifier;
|
|
const immediate = resolveExternalModuleName(node, moduleSpecifier);
|
|
const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
|
|
function getTargetOfNamespaceExport(node: NamespaceExport, dontResolveAlias: boolean): Symbol | undefined {
|
|
const moduleSpecifier = node.parent.moduleSpecifier;
|
|
const immediate = moduleSpecifier && resolveExternalModuleName(node, moduleSpecifier);
|
|
const resolved = moduleSpecifier && resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
|
|
// This function creates a synthetic symbol that combines the value side of one symbol with the
|
|
// type/namespace side of another symbol. Consider this example:
|
|
//
|
|
// declare module graphics {
|
|
// interface Point {
|
|
// x: number;
|
|
// y: number;
|
|
// }
|
|
// }
|
|
// declare var graphics: {
|
|
// Point: new (x: number, y: number) => graphics.Point;
|
|
// }
|
|
// declare module "graphics" {
|
|
// export = graphics;
|
|
// }
|
|
//
|
|
// An 'import { Point } from "graphics"' needs to create a symbol that combines the value side 'Point'
|
|
// property with the type/namespace side interface 'Point'.
|
|
function combineValueAndTypeSymbols(valueSymbol: Symbol, typeSymbol: Symbol): Symbol {
|
|
if (valueSymbol === unknownSymbol && typeSymbol === unknownSymbol) {
|
|
return unknownSymbol;
|
|
}
|
|
if (valueSymbol.flags & (SymbolFlags.Type | SymbolFlags.Namespace)) {
|
|
return valueSymbol;
|
|
}
|
|
const result = createSymbol(valueSymbol.flags | typeSymbol.flags, valueSymbol.escapedName);
|
|
result.declarations = deduplicate(concatenate(valueSymbol.declarations, typeSymbol.declarations), equateValues);
|
|
result.parent = valueSymbol.parent || typeSymbol.parent;
|
|
if (valueSymbol.valueDeclaration) result.valueDeclaration = valueSymbol.valueDeclaration;
|
|
if (typeSymbol.members) result.members = cloneMap(typeSymbol.members);
|
|
if (valueSymbol.exports) result.exports = cloneMap(valueSymbol.exports);
|
|
return result;
|
|
}
|
|
|
|
function getExportOfModule(symbol: Symbol, specifier: ImportOrExportSpecifier, dontResolveAlias: boolean): Symbol | undefined {
|
|
if (symbol.flags & SymbolFlags.Module) {
|
|
const name = (specifier.propertyName ?? specifier.name).escapedText;
|
|
const exportSymbol = getExportsOfSymbol(symbol).get(name);
|
|
const resolved = resolveSymbol(exportSymbol, dontResolveAlias);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
}
|
|
|
|
function getPropertyOfVariable(symbol: Symbol, name: __String): Symbol | undefined {
|
|
if (symbol.flags & SymbolFlags.Variable) {
|
|
const typeAnnotation = (<VariableDeclaration>symbol.valueDeclaration).type;
|
|
if (typeAnnotation) {
|
|
return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name));
|
|
}
|
|
}
|
|
}
|
|
|
|
function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration, specifier: ImportOrExportSpecifier, dontResolveAlias = false): Symbol | undefined {
|
|
const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!)!; // TODO: GH#18217
|
|
const name = specifier.propertyName || specifier.name;
|
|
const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || compilerOptions.esModuleInterop);
|
|
const targetSymbol = resolveESModuleSymbol(moduleSymbol, node.moduleSpecifier!, dontResolveAlias, suppressInteropError);
|
|
if (targetSymbol) {
|
|
if (name.escapedText) {
|
|
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
|
|
return moduleSymbol;
|
|
}
|
|
|
|
let symbolFromVariable: Symbol | undefined;
|
|
// First check if module was specified with "export=". If so, get the member from the resolved type
|
|
if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) {
|
|
symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText);
|
|
}
|
|
else {
|
|
symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText);
|
|
}
|
|
|
|
// if symbolFromVariable is export - get its final target
|
|
symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias);
|
|
let symbolFromModule = getExportOfModule(targetSymbol, specifier, dontResolveAlias);
|
|
if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) {
|
|
const file = find(moduleSymbol.declarations, isSourceFile);
|
|
if (canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias)) {
|
|
symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
|
|
}
|
|
}
|
|
|
|
const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ?
|
|
combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) :
|
|
symbolFromModule || symbolFromVariable;
|
|
if (!symbol) {
|
|
const moduleName = getFullyQualifiedName(moduleSymbol, node);
|
|
const declarationName = declarationNameToString(name);
|
|
const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol);
|
|
if (suggestion !== undefined) {
|
|
const suggestionName = symbolToString(suggestion);
|
|
const diagnostic = error(name, Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_2, moduleName, declarationName, suggestionName);
|
|
if (suggestion.valueDeclaration) {
|
|
addRelatedInfo(diagnostic,
|
|
createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
if (moduleSymbol.exports?.has(InternalSymbolName.Default)) {
|
|
error(
|
|
name,
|
|
Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead,
|
|
moduleName,
|
|
declarationName
|
|
);
|
|
}
|
|
else {
|
|
reportNonExportedMember(name, declarationName, moduleSymbol, moduleName);
|
|
}
|
|
}
|
|
}
|
|
return symbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
function reportNonExportedMember(name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void {
|
|
const localSymbol = moduleSymbol.valueDeclaration.locals?.get(name.escapedText);
|
|
const exports = moduleSymbol.exports;
|
|
|
|
if (localSymbol) {
|
|
const exportedSymbol = exports && !exports.has(InternalSymbolName.ExportEquals)
|
|
? find(symbolsToArray(exports), symbol => !!getSymbolIfSameReference(symbol, localSymbol))
|
|
: undefined;
|
|
const diagnostic = exportedSymbol
|
|
? error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_exported_as_2, moduleName, declarationName, symbolToString(exportedSymbol))
|
|
: error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported, moduleName, declarationName);
|
|
|
|
addRelatedInfo(diagnostic,
|
|
...map(localSymbol.declarations, (decl, index) =>
|
|
createDiagnosticForNode(decl, index === 0 ? Diagnostics._0_is_declared_here : Diagnostics.and_here, declarationName)));
|
|
}
|
|
else {
|
|
error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName);
|
|
}
|
|
}
|
|
|
|
function getTargetOfImportSpecifier(node: ImportSpecifier, dontResolveAlias: boolean): Symbol | undefined {
|
|
const resolved = getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
|
|
function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol {
|
|
const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
|
|
function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) {
|
|
const resolved = node.parent.parent.moduleSpecifier ?
|
|
getExternalModuleMember(node.parent.parent, node, dontResolveAlias) :
|
|
resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
|
|
function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined {
|
|
const expression = isExportAssignment(node) ? node.expression : node.right;
|
|
const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias);
|
|
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
|
|
return resolved;
|
|
}
|
|
|
|
function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) {
|
|
if (isClassExpression(expression)) {
|
|
return checkExpressionCached(expression).symbol;
|
|
}
|
|
if (!isEntityName(expression) && !isEntityNameExpression(expression)) {
|
|
return undefined;
|
|
}
|
|
const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias);
|
|
if (aliasLike) {
|
|
return aliasLike;
|
|
}
|
|
checkExpressionCached(expression);
|
|
return getNodeLinks(expression).resolvedSymbol;
|
|
}
|
|
|
|
function getTargetOfPropertyAssignment(node: PropertyAssignment, dontRecursivelyResolve: boolean): Symbol | undefined {
|
|
const expression = node.initializer;
|
|
return getTargetOfAliasLikeExpression(expression, dontRecursivelyResolve);
|
|
}
|
|
|
|
function getTargetOfPropertyAccessExpression(node: PropertyAccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined {
|
|
if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) {
|
|
return undefined;
|
|
}
|
|
|
|
return getTargetOfAliasLikeExpression(node.parent.right, dontRecursivelyResolve);
|
|
}
|
|
|
|
function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): Symbol | undefined {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
return getTargetOfImportEqualsDeclaration(<ImportEqualsDeclaration>node, dontRecursivelyResolve);
|
|
case SyntaxKind.ImportClause:
|
|
return getTargetOfImportClause(<ImportClause>node, dontRecursivelyResolve);
|
|
case SyntaxKind.NamespaceImport:
|
|
return getTargetOfNamespaceImport(<NamespaceImport>node, dontRecursivelyResolve);
|
|
case SyntaxKind.NamespaceExport:
|
|
return getTargetOfNamespaceExport(<NamespaceExport>node, dontRecursivelyResolve);
|
|
case SyntaxKind.ImportSpecifier:
|
|
return getTargetOfImportSpecifier(<ImportSpecifier>node, dontRecursivelyResolve);
|
|
case SyntaxKind.ExportSpecifier:
|
|
return getTargetOfExportSpecifier(<ExportSpecifier>node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve);
|
|
case SyntaxKind.ExportAssignment:
|
|
case SyntaxKind.BinaryExpression:
|
|
return getTargetOfExportAssignment((<ExportAssignment | BinaryExpression>node), dontRecursivelyResolve);
|
|
case SyntaxKind.NamespaceExportDeclaration:
|
|
return getTargetOfNamespaceExportDeclaration(<NamespaceExportDeclaration>node, dontRecursivelyResolve);
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve);
|
|
case SyntaxKind.PropertyAssignment:
|
|
return getTargetOfPropertyAssignment(node as PropertyAssignment, dontRecursivelyResolve);
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
return getTargetOfPropertyAccessExpression(node as PropertyAccessExpression, dontRecursivelyResolve);
|
|
default:
|
|
return Debug.fail();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates that a symbol is an alias that does not merge with a local declaration.
|
|
* OR Is a JSContainer which may merge an alias with a local declaration
|
|
*/
|
|
function isNonLocalAlias(symbol: Symbol | undefined, excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace): symbol is Symbol {
|
|
if (!symbol) return false;
|
|
return (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias || !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment);
|
|
}
|
|
|
|
function resolveSymbol(symbol: Symbol, dontResolveAlias?: boolean): Symbol;
|
|
function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined;
|
|
function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined {
|
|
return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol;
|
|
}
|
|
|
|
function resolveAlias(symbol: Symbol): Symbol {
|
|
Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here.");
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.target) {
|
|
links.target = resolvingSymbol;
|
|
const node = getDeclarationOfAliasSymbol(symbol);
|
|
if (!node) return Debug.fail();
|
|
const target = getTargetOfAliasDeclaration(node);
|
|
if (links.target === resolvingSymbol) {
|
|
links.target = target || unknownSymbol;
|
|
}
|
|
else {
|
|
error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol));
|
|
}
|
|
}
|
|
else if (links.target === resolvingSymbol) {
|
|
links.target = unknownSymbol;
|
|
}
|
|
return links.target;
|
|
}
|
|
|
|
function tryResolveAlias(symbol: Symbol): Symbol | undefined {
|
|
const links = getSymbolLinks(symbol);
|
|
if (links.target !== resolvingSymbol) {
|
|
return resolveAlias(symbol);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Marks a symbol as type-only if its declaration is syntactically type-only.
|
|
* If it is not itself marked type-only, but resolves to a type-only alias
|
|
* somewhere in its resolution chain, save a reference to the type-only alias declaration
|
|
* so the alias _not_ marked type-only can be identified as _transitively_ type-only.
|
|
*
|
|
* This function is called on each alias declaration that could be type-only or resolve to
|
|
* another type-only alias during `resolveAlias`, so that later, when an alias is used in a
|
|
* JS-emitting expression, we can quickly determine if that symbol is effectively type-only
|
|
* and issue an error if so.
|
|
*
|
|
* @param aliasDeclaration The alias declaration not marked as type-only
|
|
* has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified
|
|
* names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the
|
|
* import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b`
|
|
* must still be checked for a type-only marker, overwriting the previous negative result if found.
|
|
* @param immediateTarget The symbol to which the alias declaration immediately resolves
|
|
* @param finalTarget The symbol to which the alias declaration ultimately resolves
|
|
* @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration`
|
|
*/
|
|
function markSymbolOfAliasDeclarationIfTypeOnly(
|
|
aliasDeclaration: Declaration | undefined,
|
|
immediateTarget: Symbol | undefined,
|
|
finalTarget: Symbol | undefined,
|
|
overwriteEmpty: boolean,
|
|
): boolean {
|
|
if (!aliasDeclaration) return false;
|
|
|
|
// If the declaration itself is type-only, mark it and return.
|
|
// No need to check what it resolves to.
|
|
const sourceSymbol = getSymbolOfNode(aliasDeclaration);
|
|
if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) {
|
|
const links = getSymbolLinks(sourceSymbol);
|
|
links.typeOnlyDeclaration = aliasDeclaration;
|
|
return true;
|
|
}
|
|
|
|
const links = getSymbolLinks(sourceSymbol);
|
|
return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty)
|
|
|| markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty);
|
|
}
|
|
|
|
function markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks: SymbolLinks, target: Symbol | undefined, overwriteEmpty: boolean): boolean {
|
|
if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) {
|
|
const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target;
|
|
const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration);
|
|
aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false;
|
|
}
|
|
return !!aliasDeclarationLinks.typeOnlyDeclaration;
|
|
}
|
|
|
|
/** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */
|
|
function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyCompatibleAliasDeclaration | undefined {
|
|
if (!(symbol.flags & SymbolFlags.Alias)) {
|
|
return undefined;
|
|
}
|
|
const links = getSymbolLinks(symbol);
|
|
return links.typeOnlyDeclaration || undefined;
|
|
}
|
|
|
|
function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) {
|
|
const symbol = getSymbolOfNode(node);
|
|
const target = resolveAlias(symbol);
|
|
if (target) {
|
|
const markAlias = target === unknownSymbol ||
|
|
((target.flags & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target) && !getTypeOnlyAliasDeclaration(symbol));
|
|
|
|
if (markAlias) {
|
|
markAliasSymbolAsReferenced(symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
// When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until
|
|
// we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of
|
|
// the alias as an expression (which recursively takes us back here if the target references another alias).
|
|
function markAliasSymbolAsReferenced(symbol: Symbol) {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.referenced) {
|
|
links.referenced = true;
|
|
const node = getDeclarationOfAliasSymbol(symbol);
|
|
if (!node) return Debug.fail();
|
|
// We defer checking of the reference of an `import =` until the import itself is referenced,
|
|
// This way a chain of imports can be elided if ultimately the final input is only used in a type
|
|
// position.
|
|
if (isInternalModuleImportEqualsDeclaration(node)) {
|
|
const target = resolveSymbol(symbol);
|
|
if (target === unknownSymbol || target.flags & SymbolFlags.Value) {
|
|
// import foo = <symbol>
|
|
checkExpressionCached(<Expression>node.moduleReference);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Aliases that resolve to const enums are not marked as referenced because they are not emitted,
|
|
// but their usage in value positions must be tracked to determine if the import can be type-only.
|
|
function markConstEnumAliasAsReferenced(symbol: Symbol) {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.constEnumReferenced) {
|
|
links.constEnumReferenced = true;
|
|
}
|
|
}
|
|
|
|
// This function is only for imports with entity names
|
|
function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): Symbol | undefined {
|
|
// There are three things we might try to look for. In the following examples,
|
|
// the search term is enclosed in |...|:
|
|
//
|
|
// import a = |b|; // Namespace
|
|
// import a = |b.c|; // Value, type, namespace
|
|
// import a = |b.c|.d; // Namespace
|
|
if (entityName.kind === SyntaxKind.Identifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName)) {
|
|
entityName = <QualifiedName>entityName.parent;
|
|
}
|
|
// Check for case 1 and 3 in the above example
|
|
if (entityName.kind === SyntaxKind.Identifier || entityName.parent.kind === SyntaxKind.QualifiedName) {
|
|
return resolveEntityName(entityName, SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias);
|
|
}
|
|
else {
|
|
// Case 2 in above example
|
|
// entityName.kind could be a QualifiedName or a Missing identifier
|
|
Debug.assert(entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration);
|
|
return resolveEntityName(entityName, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias);
|
|
}
|
|
}
|
|
|
|
function getFullyQualifiedName(symbol: Symbol, containingLocation?: Node): string {
|
|
return symbol.parent ? getFullyQualifiedName(symbol.parent, containingLocation) + "." + symbolToString(symbol) : symbolToString(symbol, containingLocation, /*meaning*/ undefined, SymbolFormatFlags.DoNotIncludeSymbolChain | SymbolFormatFlags.AllowAnyNodeKind);
|
|
}
|
|
|
|
/**
|
|
* Resolves a qualified name and any involved aliases.
|
|
*/
|
|
function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined {
|
|
if (nodeIsMissing(name)) {
|
|
return undefined;
|
|
}
|
|
|
|
const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0);
|
|
let symbol: Symbol | undefined;
|
|
if (name.kind === SyntaxKind.Identifier) {
|
|
const message = meaning === namespaceMeaning || nodeIsSynthesized(name) ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name));
|
|
const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined;
|
|
symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true);
|
|
if (!symbol) {
|
|
return symbolFromJSPrototype;
|
|
}
|
|
}
|
|
else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) {
|
|
const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression;
|
|
const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name;
|
|
let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location);
|
|
if (!namespace || nodeIsMissing(right)) {
|
|
return undefined;
|
|
}
|
|
else if (namespace === unknownSymbol) {
|
|
return namespace;
|
|
}
|
|
if (isInJSFile(name)) {
|
|
if (namespace.valueDeclaration &&
|
|
isVariableDeclaration(namespace.valueDeclaration) &&
|
|
namespace.valueDeclaration.initializer &&
|
|
isCommonJsRequire(namespace.valueDeclaration.initializer)) {
|
|
const moduleName = (namespace.valueDeclaration.initializer as CallExpression).arguments[0] as StringLiteral;
|
|
const moduleSym = resolveExternalModuleName(moduleName, moduleName);
|
|
if (moduleSym) {
|
|
const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym);
|
|
if (resolvedModuleSymbol) {
|
|
namespace = resolvedModuleSymbol;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
symbol = getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning);
|
|
if (!symbol) {
|
|
if (!ignoreErrors) {
|
|
error(right, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(namespace), declarationNameToString(right));
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
else {
|
|
throw Debug.assertNever(name, "Unknown entity name kind.");
|
|
}
|
|
Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here.");
|
|
if (!nodeIsSynthesized(name) && isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) {
|
|
markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true);
|
|
}
|
|
return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol);
|
|
}
|
|
|
|
/**
|
|
* 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too.
|
|
* Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so
|
|
* name resolution won't work either.
|
|
* 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too.
|
|
*/
|
|
function resolveEntityNameFromAssignmentDeclaration(name: Identifier, meaning: SymbolFlags) {
|
|
if (isJSDocTypeReference(name.parent)) {
|
|
const secondaryLocation = getAssignmentDeclarationLocation(name.parent);
|
|
if (secondaryLocation) {
|
|
return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getAssignmentDeclarationLocation(node: TypeReferenceNode): Node | undefined {
|
|
const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node));
|
|
if (typeAlias) {
|
|
return;
|
|
}
|
|
const host = getJSDocHost(node);
|
|
if (isExpressionStatement(host) &&
|
|
isBinaryExpression(host.expression) &&
|
|
getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) {
|
|
// X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration
|
|
const symbol = getSymbolOfNode(host.expression.left);
|
|
if (symbol) {
|
|
return getDeclarationOfJSPrototypeContainer(symbol);
|
|
}
|
|
}
|
|
if ((isObjectLiteralMethod(host) || isPropertyAssignment(host)) &&
|
|
isBinaryExpression(host.parent.parent) &&
|
|
getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) {
|
|
// X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration
|
|
const symbol = getSymbolOfNode(host.parent.parent.left);
|
|
if (symbol) {
|
|
return getDeclarationOfJSPrototypeContainer(symbol);
|
|
}
|
|
}
|
|
const sig = getEffectiveJSDocHost(node);
|
|
if (sig && isFunctionLike(sig)) {
|
|
const symbol = getSymbolOfNode(sig);
|
|
return symbol && symbol.valueDeclaration;
|
|
}
|
|
}
|
|
|
|
function getDeclarationOfJSPrototypeContainer(symbol: Symbol) {
|
|
const decl = symbol.parent!.valueDeclaration;
|
|
if (!decl) {
|
|
return undefined;
|
|
}
|
|
const initializer = isAssignmentDeclaration(decl) ? getAssignedExpandoInitializer(decl) :
|
|
hasOnlyExpressionInitializer(decl) ? getDeclaredExpandoInitializer(decl) :
|
|
undefined;
|
|
return initializer || decl;
|
|
}
|
|
|
|
/**
|
|
* Get the real symbol of a declaration with an expando initializer.
|
|
*
|
|
* Normally, declarations have an associated symbol, but when a declaration has an expando
|
|
* initializer, the expando's symbol is the one that has all the members merged into it.
|
|
*/
|
|
function getExpandoSymbol(symbol: Symbol): Symbol | undefined {
|
|
const decl = symbol.valueDeclaration;
|
|
if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias || getExpandoInitializer(decl, /*isPrototypeAssignment*/ false)) {
|
|
return undefined;
|
|
}
|
|
const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl);
|
|
if (init) {
|
|
const initSymbol = getSymbolOfNode(init);
|
|
if (initSymbol) {
|
|
return mergeJSSymbols(initSymbol, symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined {
|
|
return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : Diagnostics.Cannot_find_module_0);
|
|
}
|
|
|
|
function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): Symbol | undefined {
|
|
return isStringLiteralLike(moduleReferenceExpression)
|
|
? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation)
|
|
: undefined;
|
|
}
|
|
|
|
function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined {
|
|
if (startsWith(moduleReference, "@types/")) {
|
|
const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1;
|
|
const withoutAtTypePrefix = removePrefix(moduleReference, "@types/");
|
|
error(errorNode, diag, withoutAtTypePrefix, moduleReference);
|
|
}
|
|
|
|
const ambientModule = tryFindAmbientModule(moduleReference, /*withAugmentations*/ true);
|
|
if (ambientModule) {
|
|
return ambientModule;
|
|
}
|
|
const currentSourceFile = getSourceFileOfNode(location);
|
|
const resolvedModule = getResolvedModule(currentSourceFile, moduleReference)!; // TODO: GH#18217
|
|
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule);
|
|
const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName);
|
|
if (sourceFile) {
|
|
if (sourceFile.symbol) {
|
|
if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) {
|
|
errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference);
|
|
}
|
|
// merged symbol is module declaration symbol combined with all augmentations
|
|
return getMergedSymbol(sourceFile.symbol);
|
|
}
|
|
if (moduleNotFoundError) {
|
|
// report errors only if it was requested
|
|
error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
if (patternAmbientModules) {
|
|
const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference);
|
|
if (pattern) {
|
|
// If the module reference matched a pattern ambient module ('*.foo') but there's also a
|
|
// module augmentation by the specific name requested ('a.foo'), we store the merged symbol
|
|
// by the augmentation name ('a.foo'), because asking for *.foo should not give you exports
|
|
// from a.foo.
|
|
const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference);
|
|
if (augmentation) {
|
|
return getMergedSymbol(augmentation);
|
|
}
|
|
return getMergedSymbol(pattern.symbol);
|
|
}
|
|
}
|
|
|
|
// May be an untyped module. If so, ignore resolutionDiagnostic.
|
|
if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) {
|
|
if (isForAugmentation) {
|
|
const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented;
|
|
error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName);
|
|
}
|
|
else {
|
|
errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule, moduleReference);
|
|
}
|
|
// Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first.
|
|
return undefined;
|
|
}
|
|
|
|
if (moduleNotFoundError) {
|
|
// See if this was possibly a projectReference redirect
|
|
if (resolvedModule) {
|
|
const redirect = host.getProjectReferenceRedirect(resolvedModule.resolvedFileName);
|
|
if (redirect) {
|
|
error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, resolvedModule.resolvedFileName);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
if (resolutionDiagnostic) {
|
|
error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName);
|
|
}
|
|
else {
|
|
const tsExtension = tryExtractTSExtension(moduleReference);
|
|
if (tsExtension) {
|
|
const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead;
|
|
error(errorNode, diag, tsExtension, removeExtension(moduleReference, tsExtension));
|
|
}
|
|
else if (!compilerOptions.resolveJsonModule &&
|
|
fileExtensionIs(moduleReference, Extension.Json) &&
|
|
getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs &&
|
|
hasJsonModuleEmitEnabled(compilerOptions)) {
|
|
error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference);
|
|
}
|
|
else {
|
|
error(errorNode, moduleNotFoundError, moduleReference);
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
|
|
const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId
|
|
? typesPackageExists(packageId.name)
|
|
? chainDiagnosticMessages(
|
|
/*details*/ undefined,
|
|
Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1,
|
|
packageId.name, mangleScopedPackageName(packageId.name))
|
|
: chainDiagnosticMessages(
|
|
/*details*/ undefined,
|
|
Diagnostics.Try_npm_install_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
|
|
moduleReference,
|
|
mangleScopedPackageName(packageId.name))
|
|
: undefined;
|
|
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
|
|
errorInfo,
|
|
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
|
|
moduleReference,
|
|
resolvedFileName));
|
|
}
|
|
function typesPackageExists(packageName: string): boolean {
|
|
return getPackagesSet().has(getTypesPackageName(packageName));
|
|
}
|
|
|
|
function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol;
|
|
function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined;
|
|
function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol {
|
|
if (moduleSymbol) {
|
|
const exportEquals = resolveSymbol(moduleSymbol.exports!.get(InternalSymbolName.ExportEquals), dontResolveAlias);
|
|
const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol));
|
|
return getMergedSymbol(exported) || moduleSymbol;
|
|
}
|
|
return undefined!;
|
|
}
|
|
|
|
function getCommonJsExportEquals(exported: Symbol | undefined, moduleSymbol: Symbol): Symbol | undefined {
|
|
if (!exported || exported === unknownSymbol || exported === moduleSymbol || moduleSymbol.exports!.size === 1 || exported.flags & SymbolFlags.Alias) {
|
|
return exported;
|
|
}
|
|
const links = getSymbolLinks(exported);
|
|
if (links.cjsExportMerged) {
|
|
return links.cjsExportMerged;
|
|
}
|
|
const merged = exported.flags & SymbolFlags.Transient ? exported : cloneSymbol(exported);
|
|
merged.flags = merged.flags | SymbolFlags.ValueModule;
|
|
if (merged.exports === undefined) {
|
|
merged.exports = createSymbolTable();
|
|
}
|
|
moduleSymbol.exports!.forEach((s, name) => {
|
|
if (name === InternalSymbolName.ExportEquals) return;
|
|
merged.exports!.set(name, merged.exports!.has(name) ? mergeSymbol(merged.exports!.get(name)!, s) : s);
|
|
});
|
|
getSymbolLinks(merged).cjsExportMerged = merged;
|
|
return links.cjsExportMerged = merged;
|
|
}
|
|
|
|
// An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export ='
|
|
// references a symbol that is at least declared as a module or a variable. The target of the 'export =' may
|
|
// combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable).
|
|
function resolveESModuleSymbol(moduleSymbol: Symbol | undefined, referencingLocation: Node, dontResolveAlias: boolean, suppressInteropError: boolean): Symbol | undefined {
|
|
const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias);
|
|
|
|
if (!dontResolveAlias && symbol) {
|
|
if (!suppressInteropError && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) && !getDeclarationOfKind(symbol, SyntaxKind.SourceFile)) {
|
|
const compilerOptionName = moduleKind >= ModuleKind.ES2015
|
|
? "allowSyntheticDefaultImports"
|
|
: "esModuleInterop";
|
|
|
|
error(referencingLocation, Diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName);
|
|
|
|
return symbol;
|
|
}
|
|
|
|
if (compilerOptions.esModuleInterop) {
|
|
const referenceParent = referencingLocation.parent;
|
|
if (
|
|
(isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) ||
|
|
isImportCall(referenceParent)
|
|
) {
|
|
const type = getTypeOfSymbol(symbol);
|
|
let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call);
|
|
if (!sigs || !sigs.length) {
|
|
sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct);
|
|
}
|
|
if (sigs && sigs.length) {
|
|
const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!);
|
|
// Create a new symbol which has the module's type less the call and construct signatures
|
|
const result = createSymbol(symbol.flags, symbol.escapedName);
|
|
result.declarations = symbol.declarations ? symbol.declarations.slice() : [];
|
|
result.parent = symbol.parent;
|
|
result.target = symbol;
|
|
result.originatingImport = referenceParent;
|
|
if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration;
|
|
if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true;
|
|
if (symbol.members) result.members = cloneMap(symbol.members);
|
|
if (symbol.exports) result.exports = cloneMap(symbol.exports);
|
|
const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above
|
|
result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.stringIndexInfo, resolvedModuleType.numberIndexInfo);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return symbol;
|
|
}
|
|
|
|
function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean {
|
|
return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined;
|
|
}
|
|
|
|
function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] {
|
|
return symbolsToArray(getExportsOfModule(moduleSymbol));
|
|
}
|
|
|
|
function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] {
|
|
const exports = getExportsOfModuleAsArray(moduleSymbol);
|
|
const exportEquals = resolveExternalModuleSymbol(moduleSymbol);
|
|
if (exportEquals !== moduleSymbol) {
|
|
addRange(exports, getPropertiesOfType(getTypeOfSymbol(exportEquals)));
|
|
}
|
|
return exports;
|
|
}
|
|
|
|
function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined {
|
|
const symbolTable = getExportsOfModule(moduleSymbol);
|
|
if (symbolTable) {
|
|
return symbolTable.get(memberName);
|
|
}
|
|
}
|
|
|
|
function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined {
|
|
const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol);
|
|
if (symbol) {
|
|
return symbol;
|
|
}
|
|
|
|
const exportEquals = resolveExternalModuleSymbol(moduleSymbol);
|
|
if (exportEquals === moduleSymbol) {
|
|
return undefined;
|
|
}
|
|
|
|
const type = getTypeOfSymbol(exportEquals);
|
|
return type.flags & TypeFlags.Primitive ||
|
|
getObjectFlags(type) & ObjectFlags.Class ||
|
|
isArrayOrTupleLikeType(type)
|
|
? undefined
|
|
: getPropertyOfType(type, memberName);
|
|
}
|
|
|
|
function getExportsOfSymbol(symbol: Symbol): SymbolTable {
|
|
return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) :
|
|
symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) :
|
|
symbol.exports || emptySymbols;
|
|
}
|
|
|
|
function getExportsOfModule(moduleSymbol: Symbol): SymbolTable {
|
|
const links = getSymbolLinks(moduleSymbol);
|
|
return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol));
|
|
}
|
|
|
|
interface ExportCollisionTracker {
|
|
specifierText: string;
|
|
exportsWithDuplicate: ExportDeclaration[];
|
|
}
|
|
|
|
type ExportCollisionTrackerTable = UnderscoreEscapedMap<ExportCollisionTracker>;
|
|
|
|
/**
|
|
* Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument
|
|
* Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables
|
|
*/
|
|
function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) {
|
|
if (!source) return;
|
|
source.forEach((sourceSymbol, id) => {
|
|
if (id === InternalSymbolName.Default) return;
|
|
|
|
const targetSymbol = target.get(id);
|
|
if (!targetSymbol) {
|
|
target.set(id, sourceSymbol);
|
|
if (lookupTable && exportNode) {
|
|
lookupTable.set(id, {
|
|
specifierText: getTextOfNode(exportNode.moduleSpecifier!)
|
|
} as ExportCollisionTracker);
|
|
}
|
|
}
|
|
else if (lookupTable && exportNode && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) {
|
|
const collisionTracker = lookupTable.get(id)!;
|
|
if (!collisionTracker.exportsWithDuplicate) {
|
|
collisionTracker.exportsWithDuplicate = [exportNode];
|
|
}
|
|
else {
|
|
collisionTracker.exportsWithDuplicate.push(exportNode);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function getExportsOfModuleWorker(moduleSymbol: Symbol): SymbolTable {
|
|
const visitedSymbols: Symbol[] = [];
|
|
|
|
// A module defined by an 'export=' consists of one export that needs to be resolved
|
|
moduleSymbol = resolveExternalModuleSymbol(moduleSymbol);
|
|
|
|
return visit(moduleSymbol) || emptySymbols;
|
|
|
|
// The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example,
|
|
// module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error.
|
|
function visit(symbol: Symbol | undefined): SymbolTable | undefined {
|
|
if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) {
|
|
return;
|
|
}
|
|
const symbols = cloneMap(symbol.exports);
|
|
// All export * declarations are collected in an __export symbol by the binder
|
|
const exportStars = symbol.exports.get(InternalSymbolName.ExportStar);
|
|
if (exportStars) {
|
|
const nestedSymbols = createSymbolTable();
|
|
const lookupTable = createMap<ExportCollisionTracker>() as ExportCollisionTrackerTable;
|
|
for (const node of exportStars.declarations) {
|
|
const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!);
|
|
const exportedSymbols = visit(resolvedModule);
|
|
extendExportSymbols(
|
|
nestedSymbols,
|
|
exportedSymbols,
|
|
lookupTable,
|
|
node as ExportDeclaration
|
|
);
|
|
}
|
|
lookupTable.forEach(({ exportsWithDuplicate }, id) => {
|
|
// It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself
|
|
if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(id)) {
|
|
return;
|
|
}
|
|
for (const node of exportsWithDuplicate) {
|
|
diagnostics.add(createDiagnosticForNode(
|
|
node,
|
|
Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity,
|
|
lookupTable.get(id)!.specifierText,
|
|
unescapeLeadingUnderscores(id)
|
|
));
|
|
}
|
|
});
|
|
extendExportSymbols(symbols, nestedSymbols);
|
|
}
|
|
return symbols;
|
|
}
|
|
}
|
|
|
|
function getMergedSymbol(symbol: Symbol): Symbol;
|
|
function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined;
|
|
function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined {
|
|
let merged: Symbol;
|
|
return symbol && symbol.mergeId && (merged = mergedSymbols[symbol.mergeId]) ? merged : symbol;
|
|
}
|
|
|
|
function getSymbolOfNode(node: Declaration): Symbol;
|
|
function getSymbolOfNode(node: Node): Symbol | undefined;
|
|
function getSymbolOfNode(node: Node): Symbol | undefined {
|
|
return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol));
|
|
}
|
|
|
|
function getParentOfSymbol(symbol: Symbol): Symbol | undefined {
|
|
return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent));
|
|
}
|
|
|
|
function getAlternativeContainingModules(symbol: Symbol, enclosingDeclaration: Node): Symbol[] {
|
|
const containingFile = getSourceFileOfNode(enclosingDeclaration);
|
|
const id = "" + getNodeId(containingFile);
|
|
const links = getSymbolLinks(symbol);
|
|
let results: Symbol[] | undefined;
|
|
if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) {
|
|
return results;
|
|
}
|
|
if (containingFile && containingFile.imports) {
|
|
// Try to make an import using an import already in the enclosing file, if possible
|
|
for (const importRef of containingFile.imports) {
|
|
if (nodeIsSynthesized(importRef)) continue; // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error
|
|
const resolvedModule = resolveExternalModuleName(enclosingDeclaration, importRef, /*ignoreErrors*/ true);
|
|
if (!resolvedModule) continue;
|
|
const ref = getAliasForSymbolInContainer(resolvedModule, symbol);
|
|
if (!ref) continue;
|
|
results = append(results, resolvedModule);
|
|
}
|
|
if (length(results)) {
|
|
(links.extendedContainersByFile || (links.extendedContainersByFile = createMap())).set(id, results!);
|
|
return results!;
|
|
}
|
|
}
|
|
if (links.extendedContainers) {
|
|
return links.extendedContainers;
|
|
}
|
|
// No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached)
|
|
const otherFiles = host.getSourceFiles();
|
|
for (const file of otherFiles) {
|
|
if (!isExternalModule(file)) continue;
|
|
const sym = getSymbolOfNode(file);
|
|
const ref = getAliasForSymbolInContainer(sym, symbol);
|
|
if (!ref) continue;
|
|
results = append(results, sym);
|
|
}
|
|
return links.extendedContainers = results || emptyArray;
|
|
}
|
|
|
|
/**
|
|
* Attempts to find the symbol corresponding to the container a symbol is in - usually this
|
|
* is just its' `.parent`, but for locals, this value is `undefined`
|
|
*/
|
|
function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined): Symbol[] | undefined {
|
|
const container = getParentOfSymbol(symbol);
|
|
// Type parameters end up in the `members` lists but are not externally visible
|
|
if (container && !(symbol.flags & SymbolFlags.TypeParameter)) {
|
|
const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer);
|
|
const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration);
|
|
if (enclosingDeclaration && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)) {
|
|
return concatenate(concatenate([container], additionalContainers), reexportContainers); // This order expresses a preference for the real container if it is in scope
|
|
}
|
|
const res = append(additionalContainers, container);
|
|
return concatenate(res, reexportContainers);
|
|
}
|
|
const candidates = mapDefined(symbol.declarations, d => {
|
|
if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) {
|
|
return getSymbolOfNode(d.parent);
|
|
}
|
|
if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) {
|
|
if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) {
|
|
return getSymbolOfNode(getSourceFileOfNode(d));
|
|
}
|
|
checkExpressionCached(d.parent.left.expression);
|
|
return getNodeLinks(d.parent.left.expression).resolvedSymbol;
|
|
}
|
|
});
|
|
if (!length(candidates)) {
|
|
return undefined;
|
|
}
|
|
return mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined);
|
|
|
|
function fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) {
|
|
const fileSymbol = getExternalModuleContainer(d);
|
|
const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals);
|
|
return exported && container && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined;
|
|
}
|
|
}
|
|
|
|
function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) {
|
|
if (container === getParentOfSymbol(symbol)) {
|
|
// fast path, `symbol` is either already the alias or isn't aliased
|
|
return symbol;
|
|
}
|
|
// Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return
|
|
// the container itself as the alias for the symbol
|
|
const exportEquals = container.exports && container.exports.get(InternalSymbolName.ExportEquals);
|
|
if (exportEquals && getSymbolIfSameReference(exportEquals, symbol)) {
|
|
return container;
|
|
}
|
|
const exports = getExportsOfSymbol(container);
|
|
const quick = exports.get(symbol.escapedName);
|
|
if (quick && getSymbolIfSameReference(quick, symbol)) {
|
|
return quick;
|
|
}
|
|
return forEachEntry(exports, exported => {
|
|
if (getSymbolIfSameReference(exported, symbol)) {
|
|
return exported;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Checks if two symbols, through aliasing and/or merging, refer to the same thing
|
|
*/
|
|
function getSymbolIfSameReference(s1: Symbol, s2: Symbol) {
|
|
if (getMergedSymbol(resolveSymbol(getMergedSymbol(s1))) === getMergedSymbol(resolveSymbol(getMergedSymbol(s2)))) {
|
|
return s1;
|
|
}
|
|
}
|
|
|
|
function getExportSymbolOfValueSymbolIfExported(symbol: Symbol): Symbol;
|
|
function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined;
|
|
function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined {
|
|
return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 ? symbol.exportSymbol : symbol);
|
|
}
|
|
|
|
function symbolIsValue(symbol: Symbol): boolean {
|
|
return !!(symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value && !getTypeOnlyAliasDeclaration(symbol));
|
|
}
|
|
|
|
function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined {
|
|
const members = node.members;
|
|
for (const member of members) {
|
|
if (member.kind === SyntaxKind.Constructor && nodeIsPresent((<ConstructorDeclaration>member).body)) {
|
|
return <ConstructorDeclaration>member;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createType(flags: TypeFlags): Type {
|
|
const result = new Type(checker, flags);
|
|
typeCount++;
|
|
result.id = typeCount;
|
|
return result;
|
|
}
|
|
|
|
function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType {
|
|
const type = <IntrinsicType>createType(kind);
|
|
type.intrinsicName = intrinsicName;
|
|
type.objectFlags = objectFlags;
|
|
return type;
|
|
}
|
|
|
|
function createBooleanType(trueFalseTypes: readonly Type[]): IntrinsicType & UnionType {
|
|
const type = <IntrinsicType & UnionType>getUnionType(trueFalseTypes);
|
|
type.flags |= TypeFlags.Boolean;
|
|
type.intrinsicName = "boolean";
|
|
return type;
|
|
}
|
|
|
|
function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType {
|
|
const type = <ObjectType>createType(TypeFlags.Object);
|
|
type.objectFlags = objectFlags;
|
|
type.symbol = symbol!;
|
|
type.members = undefined;
|
|
type.properties = undefined;
|
|
type.callSignatures = undefined;
|
|
type.constructSignatures = undefined;
|
|
type.stringIndexInfo = undefined;
|
|
type.numberIndexInfo = undefined;
|
|
return type;
|
|
}
|
|
|
|
function createTypeofType() {
|
|
return getUnionType(arrayFrom(typeofEQFacts.keys(), getLiteralType));
|
|
}
|
|
|
|
function createTypeParameter(symbol?: Symbol) {
|
|
const type = <TypeParameter>createType(TypeFlags.TypeParameter);
|
|
if (symbol) type.symbol = symbol;
|
|
return type;
|
|
}
|
|
|
|
// A reserved member name starts with two underscores, but the third character cannot be an underscore,
|
|
// @, or #. A third underscore indicates an escaped form of an identifier that started
|
|
// with at least two underscores. The @ character indicates that the name is denoted by a well known ES
|
|
// Symbol instance and the # character indicates that the name is a PrivateIdentifier.
|
|
function isReservedMemberName(name: __String) {
|
|
return (name as string).charCodeAt(0) === CharacterCodes._ &&
|
|
(name as string).charCodeAt(1) === CharacterCodes._ &&
|
|
(name as string).charCodeAt(2) !== CharacterCodes._ &&
|
|
(name as string).charCodeAt(2) !== CharacterCodes.at &&
|
|
(name as string).charCodeAt(2) !== CharacterCodes.hash;
|
|
}
|
|
|
|
function getNamedMembers(members: SymbolTable): Symbol[] {
|
|
let result: Symbol[] | undefined;
|
|
members.forEach((symbol, id) => {
|
|
if (!isReservedMemberName(id) && symbolIsValue(symbol)) {
|
|
(result || (result = [])).push(symbol);
|
|
}
|
|
});
|
|
return result || emptyArray;
|
|
}
|
|
|
|
function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): ResolvedType {
|
|
(<ResolvedType>type).members = members;
|
|
(<ResolvedType>type).properties = members === emptySymbols ? emptyArray : getNamedMembers(members);
|
|
(<ResolvedType>type).callSignatures = callSignatures;
|
|
(<ResolvedType>type).constructSignatures = constructSignatures;
|
|
(<ResolvedType>type).stringIndexInfo = stringIndexInfo;
|
|
(<ResolvedType>type).numberIndexInfo = numberIndexInfo;
|
|
return <ResolvedType>type;
|
|
}
|
|
|
|
function createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): ResolvedType {
|
|
return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol),
|
|
members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
|
|
}
|
|
|
|
function forEachSymbolTableInScope<T>(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable) => T): T {
|
|
let result: T;
|
|
for (let location = enclosingDeclaration; location; location = location.parent) {
|
|
// Locals of a source file are not in scope (because they get merged into the global symbol table)
|
|
if (location.locals && !isGlobalSourceFile(location)) {
|
|
if (result = callback(location.locals)) {
|
|
return result;
|
|
}
|
|
}
|
|
switch (location.kind) {
|
|
case SyntaxKind.SourceFile:
|
|
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
|
|
break;
|
|
}
|
|
// falls through
|
|
case SyntaxKind.ModuleDeclaration:
|
|
const sym = getSymbolOfNode(location as ModuleDeclaration);
|
|
// `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten
|
|
// into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred
|
|
// to one another anyway)
|
|
if (result = callback(sym?.exports || emptySymbols)) {
|
|
return result;
|
|
}
|
|
break;
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
// Type parameters are bound into `members` lists so they can merge across declarations
|
|
// This is troublesome, since in all other respects, they behave like locals :cries:
|
|
// TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol
|
|
// lookup logic in terms of `resolveName` would be nice
|
|
// The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals
|
|
// These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would
|
|
// trigger resolving late-bound names, which we may already be in the process of doing while we're here!
|
|
let table: UnderscoreEscapedMap<Symbol> | undefined;
|
|
// TODO: Should this filtered table be cached in some way?
|
|
(getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols).forEach((memberSymbol, key) => {
|
|
if (memberSymbol.flags & (SymbolFlags.Type & ~SymbolFlags.Assignment)) {
|
|
(table || (table = createSymbolTable())).set(key, memberSymbol);
|
|
}
|
|
});
|
|
if (table && (result = callback(table))) {
|
|
return result;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return callback(globals);
|
|
}
|
|
|
|
function getQualifiedLeftMeaning(rightMeaning: SymbolFlags) {
|
|
// If we are looking in value space, the parent meaning is value, other wise it is namespace
|
|
return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace;
|
|
}
|
|
|
|
function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: Map<SymbolTable[]> = createMap()): Symbol[] | undefined {
|
|
if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) {
|
|
return undefined;
|
|
}
|
|
|
|
const id = "" + getSymbolId(symbol);
|
|
let visitedSymbolTables = visitedSymbolTablesMap.get(id);
|
|
if (!visitedSymbolTables) {
|
|
visitedSymbolTablesMap.set(id, visitedSymbolTables = []);
|
|
}
|
|
return forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable);
|
|
|
|
/**
|
|
* @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already)
|
|
*/
|
|
function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean): Symbol[] | undefined {
|
|
if (!pushIfUnique(visitedSymbolTables!, symbols)) {
|
|
return undefined;
|
|
}
|
|
|
|
const result = trySymbolTable(symbols, ignoreQualification);
|
|
visitedSymbolTables!.pop();
|
|
return result;
|
|
}
|
|
|
|
function canQualifySymbol(symbolFromSymbolTable: Symbol, meaning: SymbolFlags) {
|
|
// If the symbol is equivalent and doesn't need further qualification, this symbol is accessible
|
|
return !needsQualification(symbolFromSymbolTable, enclosingDeclaration, meaning) ||
|
|
// If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too
|
|
!!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing, visitedSymbolTablesMap);
|
|
}
|
|
|
|
function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol, ignoreQualification?: boolean) {
|
|
return (symbol === (resolvedAliasSymbol || symbolFromSymbolTable) || getMergedSymbol(symbol) === getMergedSymbol(resolvedAliasSymbol || symbolFromSymbolTable)) &&
|
|
// if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table)
|
|
// and if symbolFromSymbolTable or alias resolution matches the symbol,
|
|
// check the symbol can be qualified, it is only then this symbol is accessible
|
|
!some(symbolFromSymbolTable.declarations, hasNonGlobalAugmentationExternalModuleSymbol) &&
|
|
(ignoreQualification || canQualifySymbol(getMergedSymbol(symbolFromSymbolTable), meaning));
|
|
}
|
|
|
|
function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined): Symbol[] | undefined {
|
|
// If symbol is directly available by its name in the symbol table
|
|
if (isAccessible(symbols.get(symbol!.escapedName)!, /*resolvedAliasSymbol*/ undefined, ignoreQualification)) {
|
|
return [symbol!];
|
|
}
|
|
|
|
// Check if symbol is any of the aliases in scope
|
|
const result = forEachEntry(symbols, symbolFromSymbolTable => {
|
|
if (symbolFromSymbolTable.flags & SymbolFlags.Alias
|
|
&& symbolFromSymbolTable.escapedName !== InternalSymbolName.ExportEquals
|
|
&& symbolFromSymbolTable.escapedName !== InternalSymbolName.Default
|
|
&& !(isUMDExportSymbol(symbolFromSymbolTable) && enclosingDeclaration && isExternalModule(getSourceFileOfNode(enclosingDeclaration)))
|
|
// If `!useOnlyExternalAliasing`, we can use any type of alias to get the name
|
|
&& (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration))
|
|
// While exports are generally considered to be in scope, export-specifier declared symbols are _not_
|
|
// See similar comment in `resolveName` for details
|
|
&& (ignoreQualification || !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier))
|
|
) {
|
|
|
|
const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable);
|
|
const candidate = getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification);
|
|
if (candidate) {
|
|
return candidate;
|
|
}
|
|
}
|
|
if (symbolFromSymbolTable.escapedName === symbol!.escapedName && symbolFromSymbolTable.exportSymbol) {
|
|
if (isAccessible(getMergedSymbol(symbolFromSymbolTable.exportSymbol), /*aliasSymbol*/ undefined, ignoreQualification)) {
|
|
return [symbol!];
|
|
}
|
|
}
|
|
});
|
|
|
|
// If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that
|
|
return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined);
|
|
}
|
|
|
|
function getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) {
|
|
if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) {
|
|
return [symbolFromSymbolTable];
|
|
}
|
|
|
|
// Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain
|
|
// but only if the symbolFromSymbolTable can be qualified
|
|
const candidateTable = getExportsOfSymbol(resolvedImportedSymbol);
|
|
const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true);
|
|
if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) {
|
|
return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports);
|
|
}
|
|
}
|
|
}
|
|
|
|
function needsQualification(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags) {
|
|
let qualify = false;
|
|
forEachSymbolTableInScope(enclosingDeclaration, symbolTable => {
|
|
// If symbol of this name is not available in the symbol table we are ok
|
|
let symbolFromSymbolTable = getMergedSymbol(symbolTable.get(symbol.escapedName));
|
|
if (!symbolFromSymbolTable) {
|
|
// Continue to the next symbol table
|
|
return false;
|
|
}
|
|
// If the symbol with this name is present it should refer to the symbol
|
|
if (symbolFromSymbolTable === symbol) {
|
|
// No need to qualify
|
|
return true;
|
|
}
|
|
|
|
// Qualify if the symbol from symbol table has same meaning as expected
|
|
symbolFromSymbolTable = (symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable;
|
|
if (symbolFromSymbolTable.flags & meaning) {
|
|
qualify = true;
|
|
return true;
|
|
}
|
|
|
|
// Continue to the next symbol table
|
|
return false;
|
|
});
|
|
|
|
return qualify;
|
|
}
|
|
|
|
function isPropertyOrMethodDeclarationSymbol(symbol: Symbol) {
|
|
if (symbol.declarations && symbol.declarations.length) {
|
|
for (const declaration of symbol.declarations) {
|
|
switch (declaration.kind) {
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
continue;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean {
|
|
const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false);
|
|
return access.accessibility === SymbolAccessibility.Accessible;
|
|
}
|
|
|
|
function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean {
|
|
const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false);
|
|
return access.accessibility === SymbolAccessibility.Accessible;
|
|
}
|
|
|
|
function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult | undefined {
|
|
if (!length(symbols)) return;
|
|
|
|
let hadAccessibleChain: Symbol | undefined;
|
|
let earlyModuleBail = false;
|
|
for (const symbol of symbols!) {
|
|
// Symbol is accessible if it by itself is accessible
|
|
const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false);
|
|
if (accessibleSymbolChain) {
|
|
hadAccessibleChain = symbol;
|
|
const hasAccessibleDeclarations = hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible);
|
|
if (hasAccessibleDeclarations) {
|
|
return hasAccessibleDeclarations;
|
|
}
|
|
}
|
|
else {
|
|
if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) {
|
|
if (shouldComputeAliasesToMakeVisible) {
|
|
earlyModuleBail = true;
|
|
// Generally speaking, we want to use the aliases that already exist to refer to a module, if present
|
|
// In order to do so, we need to find those aliases in order to retain them in declaration emit; so
|
|
// if we are in declaration emit, we cannot use the fast path for module visibility until we've exhausted
|
|
// all other visibility options (in order to capture the possible aliases used to reference the module)
|
|
continue;
|
|
}
|
|
// Any meaning of a module symbol is always accessible via an `import` type
|
|
return {
|
|
accessibility: SymbolAccessibility.Accessible
|
|
};
|
|
}
|
|
}
|
|
|
|
// If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible.
|
|
// It could be a qualified symbol and hence verify the path
|
|
// e.g.:
|
|
// module m {
|
|
// export class c {
|
|
// }
|
|
// }
|
|
// const x: typeof m.c
|
|
// In the above example when we start with checking if typeof m.c symbol is accessible,
|
|
// we are going to see if c can be accessed in scope directly.
|
|
// But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible
|
|
// It is accessible if the parent m is accessible because then m.c can be accessed through qualification
|
|
|
|
let containers = getContainersOfSymbol(symbol, enclosingDeclaration);
|
|
// If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct
|
|
// from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however,
|
|
// we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal.
|
|
const firstDecl: Node = first(symbol.declarations);
|
|
if (!length(containers) && meaning & SymbolFlags.Value && firstDecl && isObjectLiteralExpression(firstDecl)) {
|
|
if (firstDecl.parent && isVariableDeclaration(firstDecl.parent) && firstDecl === firstDecl.parent.initializer) {
|
|
containers = [getSymbolOfNode(firstDecl.parent)];
|
|
}
|
|
}
|
|
const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible);
|
|
if (parentResult) {
|
|
return parentResult;
|
|
}
|
|
}
|
|
|
|
if (earlyModuleBail) {
|
|
return {
|
|
accessibility: SymbolAccessibility.Accessible
|
|
};
|
|
}
|
|
|
|
if (hadAccessibleChain) {
|
|
return {
|
|
accessibility: SymbolAccessibility.NotAccessible,
|
|
errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning),
|
|
errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested
|
|
*
|
|
* @param symbol a Symbol to check if accessible
|
|
* @param enclosingDeclaration a Node containing reference to the symbol
|
|
* @param meaning a SymbolFlags to check if such meaning of the symbol is accessible
|
|
* @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible
|
|
*/
|
|
function isSymbolAccessible(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult {
|
|
if (symbol && enclosingDeclaration) {
|
|
const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
|
|
// This could be a symbol that is not exported in the external module
|
|
// or it could be a symbol from different external module that is not aliased and hence cannot be named
|
|
const symbolExternalModule = forEach(symbol.declarations, getExternalModuleContainer);
|
|
if (symbolExternalModule) {
|
|
const enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration);
|
|
if (symbolExternalModule !== enclosingExternalModule) {
|
|
// name from different external module that is not visible
|
|
return {
|
|
accessibility: SymbolAccessibility.CannotBeNamed,
|
|
errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning),
|
|
errorModuleName: symbolToString(symbolExternalModule)
|
|
};
|
|
}
|
|
}
|
|
|
|
// Just a local name that is not accessible
|
|
return {
|
|
accessibility: SymbolAccessibility.NotAccessible,
|
|
errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning),
|
|
};
|
|
}
|
|
|
|
return { accessibility: SymbolAccessibility.Accessible };
|
|
}
|
|
|
|
function getExternalModuleContainer(declaration: Node) {
|
|
const node = findAncestor(declaration, hasExternalModuleSymbol);
|
|
return node && getSymbolOfNode(node);
|
|
}
|
|
|
|
function hasExternalModuleSymbol(declaration: Node) {
|
|
return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>declaration));
|
|
}
|
|
|
|
function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) {
|
|
return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>declaration));
|
|
}
|
|
|
|
function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined {
|
|
let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined;
|
|
if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) {
|
|
return undefined;
|
|
}
|
|
return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible };
|
|
|
|
function getIsDeclarationVisible(declaration: Declaration) {
|
|
if (!isDeclarationVisible(declaration)) {
|
|
// Mark the unexported alias as visible if its parent is visible
|
|
// because these kind of aliases can be used to name types in declaration file
|
|
|
|
const anyImportSyntax = getAnyImportSyntax(declaration);
|
|
if (anyImportSyntax &&
|
|
!hasModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export
|
|
isDeclarationVisible(anyImportSyntax.parent)) {
|
|
return addVisibleAlias(declaration, anyImportSyntax);
|
|
}
|
|
else if (isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) &&
|
|
!hasModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement
|
|
isDeclarationVisible(declaration.parent.parent.parent)) {
|
|
return addVisibleAlias(declaration, declaration.parent.parent);
|
|
}
|
|
else if (isLateVisibilityPaintedStatement(declaration) // unexported top-level statement
|
|
&& !hasModifier(declaration, ModifierFlags.Export)
|
|
&& isDeclarationVisible(declaration.parent)) {
|
|
return addVisibleAlias(declaration, declaration);
|
|
}
|
|
|
|
// Declaration is not visible
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) {
|
|
// In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types,
|
|
// we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time
|
|
// since we will do the emitting later in trackSymbol.
|
|
if (shouldComputeAliasToMakeVisible) {
|
|
getNodeLinks(declaration).isVisible = true;
|
|
aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult {
|
|
// get symbol of the first identifier of the entityName
|
|
let meaning: SymbolFlags;
|
|
if (entityName.parent.kind === SyntaxKind.TypeQuery ||
|
|
isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent) ||
|
|
entityName.parent.kind === SyntaxKind.ComputedPropertyName) {
|
|
// Typeof value
|
|
meaning = SymbolFlags.Value | SymbolFlags.ExportValue;
|
|
}
|
|
else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression ||
|
|
entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) {
|
|
// Left identifier from type reference or TypeAlias
|
|
// Entity name of the import declaration
|
|
meaning = SymbolFlags.Namespace;
|
|
}
|
|
else {
|
|
// Type Reference or TypeAlias entity = Identifier
|
|
meaning = SymbolFlags.Type;
|
|
}
|
|
|
|
const firstIdentifier = getFirstIdentifier(entityName);
|
|
const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false);
|
|
|
|
// Verify if the symbol is accessible
|
|
return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || {
|
|
accessibility: SymbolAccessibility.NotAccessible,
|
|
errorSymbolName: getTextOfNode(firstIdentifier),
|
|
errorNode: firstIdentifier
|
|
};
|
|
}
|
|
|
|
function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string {
|
|
let nodeFlags = NodeBuilderFlags.IgnoreErrors;
|
|
if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) {
|
|
nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing;
|
|
}
|
|
if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) {
|
|
nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName;
|
|
}
|
|
if (flags & SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope) {
|
|
nodeFlags |= NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope;
|
|
}
|
|
if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) {
|
|
nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain;
|
|
}
|
|
const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName;
|
|
return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker);
|
|
|
|
function symbolToStringWorker(writer: EmitTextWriter) {
|
|
const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217
|
|
const printer = createPrinter({ removeComments: true });
|
|
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
|
|
printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer);
|
|
return writer;
|
|
}
|
|
}
|
|
|
|
function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags = TypeFormatFlags.None, kind?: SignatureKind, writer?: EmitTextWriter): string {
|
|
return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker);
|
|
|
|
function signatureToStringWorker(writer: EmitTextWriter) {
|
|
let sigOutput: SyntaxKind;
|
|
if (flags & TypeFormatFlags.WriteArrowStyleSignature) {
|
|
sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType;
|
|
}
|
|
else {
|
|
sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature;
|
|
}
|
|
const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName);
|
|
const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true });
|
|
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
|
|
printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217
|
|
return writer;
|
|
}
|
|
}
|
|
|
|
function typeToString(type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string {
|
|
const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation;
|
|
const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer);
|
|
if (typeNode === undefined) return Debug.fail("should always get typenode");
|
|
const options = { removeComments: true };
|
|
const printer = createPrinter(options);
|
|
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
|
|
printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer);
|
|
const result = writer.getText();
|
|
|
|
const maxLength = noTruncation ? undefined : defaultMaximumTruncationLength * 2;
|
|
if (maxLength && result && result.length >= maxLength) {
|
|
return result.substr(0, maxLength - "...".length) + "...";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getTypeNamesForErrorDisplay(left: Type, right: Type): [string, string] {
|
|
let leftStr = symbolValueDeclarationIsContextSensitive(left.symbol) ? typeToString(left, left.symbol.valueDeclaration) : typeToString(left);
|
|
let rightStr = symbolValueDeclarationIsContextSensitive(right.symbol) ? typeToString(right, right.symbol.valueDeclaration) : typeToString(right);
|
|
if (leftStr === rightStr) {
|
|
leftStr = typeToString(left, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType);
|
|
rightStr = typeToString(right, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType);
|
|
}
|
|
return [leftStr, rightStr];
|
|
}
|
|
|
|
function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean {
|
|
return symbol && symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration);
|
|
}
|
|
|
|
function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags {
|
|
return flags & TypeFormatFlags.NodeBuilderFlagsMask;
|
|
}
|
|
|
|
function createNodeBuilder() {
|
|
return {
|
|
typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)),
|
|
indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, context)),
|
|
signatureToSignatureDeclaration: (signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)),
|
|
symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)),
|
|
symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => symbolToExpression(symbol, context, meaning)),
|
|
symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => typeParametersToTypeParameterDeclarations(symbol, context)),
|
|
symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => symbolToParameterDeclaration(symbol, context)),
|
|
typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)),
|
|
symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) =>
|
|
withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)),
|
|
};
|
|
|
|
function withContext<T>(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined {
|
|
Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0);
|
|
const context: NodeBuilderContext = {
|
|
enclosingDeclaration,
|
|
flags: flags || NodeBuilderFlags.None,
|
|
// If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost
|
|
tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? {
|
|
getCommonSourceDirectory: (host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "",
|
|
getSourceFiles: () => host.getSourceFiles(),
|
|
getCurrentDirectory: maybeBind(host, host.getCurrentDirectory),
|
|
getProbableSymlinks: maybeBind(host, host.getProbableSymlinks),
|
|
useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames)
|
|
} : undefined },
|
|
encounteredError: false,
|
|
visitedTypes: undefined,
|
|
symbolDepth: undefined,
|
|
inferTypeParameters: undefined,
|
|
approximateLength: 0
|
|
};
|
|
const resultingNode = cb(context);
|
|
return context.encounteredError ? undefined : resultingNode;
|
|
}
|
|
|
|
function checkTruncationLength(context: NodeBuilderContext): boolean {
|
|
if (context.truncating) return context.truncating;
|
|
return context.truncating = !(context.flags & NodeBuilderFlags.NoTruncation) && context.approximateLength > defaultMaximumTruncationLength;
|
|
}
|
|
|
|
function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode {
|
|
if (cancellationToken && cancellationToken.throwIfCancellationRequested) {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
}
|
|
const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias;
|
|
context.flags &= ~NodeBuilderFlags.InTypeAlias;
|
|
|
|
if (!type) {
|
|
context.encounteredError = true;
|
|
return undefined!; // TODO: GH#18217
|
|
}
|
|
|
|
type = getReducedType(type);
|
|
|
|
if (type.flags & TypeFlags.Any) {
|
|
context.approximateLength += 3;
|
|
return createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.Unknown) {
|
|
return createKeywordTypeNode(SyntaxKind.UnknownKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.String) {
|
|
context.approximateLength += 6;
|
|
return createKeywordTypeNode(SyntaxKind.StringKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.Number) {
|
|
context.approximateLength += 6;
|
|
return createKeywordTypeNode(SyntaxKind.NumberKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.BigInt) {
|
|
context.approximateLength += 6;
|
|
return createKeywordTypeNode(SyntaxKind.BigIntKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.Boolean) {
|
|
context.approximateLength += 7;
|
|
return createKeywordTypeNode(SyntaxKind.BooleanKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union)) {
|
|
const parentSymbol = getParentOfSymbol(type.symbol)!;
|
|
const parentName = symbolToTypeNode(parentSymbol, context, SymbolFlags.Type);
|
|
const enumLiteralName = getDeclaredTypeOfSymbol(parentSymbol) === type
|
|
? parentName
|
|
: appendReferenceToType(
|
|
parentName as TypeReferenceNode | ImportTypeNode,
|
|
createTypeReferenceNode(symbolName(type.symbol), /*typeArguments*/ undefined)
|
|
);
|
|
return enumLiteralName;
|
|
}
|
|
if (type.flags & TypeFlags.EnumLike) {
|
|
return symbolToTypeNode(type.symbol, context, SymbolFlags.Type);
|
|
}
|
|
if (type.flags & TypeFlags.StringLiteral) {
|
|
context.approximateLength += ((<StringLiteralType>type).value.length + 2);
|
|
return createLiteralTypeNode(setEmitFlags(createLiteral((<StringLiteralType>type).value, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)), EmitFlags.NoAsciiEscaping));
|
|
}
|
|
if (type.flags & TypeFlags.NumberLiteral) {
|
|
const value = (<NumberLiteralType>type).value;
|
|
context.approximateLength += ("" + value).length;
|
|
return createLiteralTypeNode(value < 0 ? createPrefix(SyntaxKind.MinusToken, createLiteral(-value)) : createLiteral(value));
|
|
}
|
|
if (type.flags & TypeFlags.BigIntLiteral) {
|
|
context.approximateLength += (pseudoBigIntToString((<BigIntLiteralType>type).value).length) + 1;
|
|
return createLiteralTypeNode((createLiteral((<BigIntLiteralType>type).value)));
|
|
}
|
|
if (type.flags & TypeFlags.BooleanLiteral) {
|
|
context.approximateLength += (<IntrinsicType>type).intrinsicName.length;
|
|
return (<IntrinsicType>type).intrinsicName === "true" ? createTrue() : createFalse();
|
|
}
|
|
if (type.flags & TypeFlags.UniqueESSymbol) {
|
|
if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) {
|
|
if (isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) {
|
|
context.approximateLength += 6;
|
|
return symbolToTypeNode(type.symbol, context, SymbolFlags.Value);
|
|
}
|
|
if (context.tracker.reportInaccessibleUniqueSymbolError) {
|
|
context.tracker.reportInaccessibleUniqueSymbolError();
|
|
}
|
|
}
|
|
context.approximateLength += 13;
|
|
return createTypeOperatorNode(SyntaxKind.UniqueKeyword, createKeywordTypeNode(SyntaxKind.SymbolKeyword));
|
|
}
|
|
if (type.flags & TypeFlags.Void) {
|
|
context.approximateLength += 4;
|
|
return createKeywordTypeNode(SyntaxKind.VoidKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.Undefined) {
|
|
context.approximateLength += 9;
|
|
return createKeywordTypeNode(SyntaxKind.UndefinedKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.Null) {
|
|
context.approximateLength += 4;
|
|
return createKeywordTypeNode(SyntaxKind.NullKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.Never) {
|
|
context.approximateLength += 5;
|
|
return createKeywordTypeNode(SyntaxKind.NeverKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.ESSymbol) {
|
|
context.approximateLength += 6;
|
|
return createKeywordTypeNode(SyntaxKind.SymbolKeyword);
|
|
}
|
|
if (type.flags & TypeFlags.NonPrimitive) {
|
|
context.approximateLength += 6;
|
|
return createKeywordTypeNode(SyntaxKind.ObjectKeyword);
|
|
}
|
|
if (isThisTypeParameter(type)) {
|
|
if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) {
|
|
if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) {
|
|
context.encounteredError = true;
|
|
}
|
|
if (context.tracker.reportInaccessibleThisError) {
|
|
context.tracker.reportInaccessibleThisError();
|
|
}
|
|
}
|
|
context.approximateLength += 4;
|
|
return createThis();
|
|
}
|
|
|
|
if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) {
|
|
const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context);
|
|
if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return createTypeReferenceNode(createIdentifier(""), typeArgumentNodes);
|
|
return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes);
|
|
}
|
|
|
|
const objectFlags = getObjectFlags(type);
|
|
|
|
if (objectFlags & ObjectFlags.Reference) {
|
|
Debug.assert(!!(type.flags & TypeFlags.Object));
|
|
return (<TypeReference>type).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode(<TypeReference>type);
|
|
}
|
|
if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) {
|
|
if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) {
|
|
context.approximateLength += (symbolName(type.symbol).length + 6);
|
|
return createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined));
|
|
}
|
|
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
|
|
type.flags & TypeFlags.TypeParameter &&
|
|
!isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) {
|
|
const name = typeParameterToName(type, context);
|
|
context.approximateLength += idText(name).length;
|
|
return createTypeReferenceNode(createIdentifier(idText(name)), /*typeArguments*/ undefined);
|
|
}
|
|
// Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter.
|
|
return type.symbol
|
|
? symbolToTypeNode(type.symbol, context, SymbolFlags.Type)
|
|
: createTypeReferenceNode(createIdentifier("?"), /*typeArguments*/ undefined);
|
|
}
|
|
if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) {
|
|
const types = type.flags & TypeFlags.Union ? formatUnionTypes((<UnionType>type).types) : (<IntersectionType>type).types;
|
|
if (length(types) === 1) {
|
|
return typeToTypeNodeHelper(types[0], context);
|
|
}
|
|
const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true);
|
|
if (typeNodes && typeNodes.length > 0) {
|
|
const unionOrIntersectionTypeNode = createUnionOrIntersectionTypeNode(type.flags & TypeFlags.Union ? SyntaxKind.UnionType : SyntaxKind.IntersectionType, typeNodes);
|
|
return unionOrIntersectionTypeNode;
|
|
}
|
|
else {
|
|
if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) {
|
|
context.encounteredError = true;
|
|
}
|
|
return undefined!; // TODO: GH#18217
|
|
}
|
|
}
|
|
if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) {
|
|
Debug.assert(!!(type.flags & TypeFlags.Object));
|
|
// The type is an object literal type.
|
|
return createAnonymousTypeNode(<ObjectType>type);
|
|
}
|
|
if (type.flags & TypeFlags.Index) {
|
|
const indexedType = (<IndexType>type).type;
|
|
context.approximateLength += 6;
|
|
const indexTypeNode = typeToTypeNodeHelper(indexedType, context);
|
|
return createTypeOperatorNode(indexTypeNode);
|
|
}
|
|
if (type.flags & TypeFlags.IndexedAccess) {
|
|
const objectTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).objectType, context);
|
|
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
|
|
context.approximateLength += 2;
|
|
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
|
|
}
|
|
if (type.flags & TypeFlags.Conditional) {
|
|
const checkTypeNode = typeToTypeNodeHelper((<ConditionalType>type).checkType, context);
|
|
const saveInferTypeParameters = context.inferTypeParameters;
|
|
context.inferTypeParameters = (<ConditionalType>type).root.inferTypeParameters;
|
|
const extendsTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
|
|
context.inferTypeParameters = saveInferTypeParameters;
|
|
const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(<ConditionalType>type), context);
|
|
const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(<ConditionalType>type), context);
|
|
context.approximateLength += 15;
|
|
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
|
|
}
|
|
if (type.flags & TypeFlags.Substitution) {
|
|
return typeToTypeNodeHelper((<SubstitutionType>type).typeVariable, context);
|
|
}
|
|
|
|
return Debug.fail("Should be unreachable.");
|
|
|
|
function createMappedTypeNodeFromType(type: MappedType) {
|
|
Debug.assert(!!(type.flags & TypeFlags.Object));
|
|
const readonlyToken = type.declaration.readonlyToken ? <ReadonlyToken | PlusToken | MinusToken>createToken(type.declaration.readonlyToken.kind) : undefined;
|
|
const questionToken = type.declaration.questionToken ? <QuestionToken | PlusToken | MinusToken>createToken(type.declaration.questionToken.kind) : undefined;
|
|
let appropriateConstraintTypeNode: TypeNode;
|
|
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
|
|
// We have a { [P in keyof T]: X }
|
|
// We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType`
|
|
appropriateConstraintTypeNode = createTypeOperatorNode(typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context));
|
|
}
|
|
else {
|
|
appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context);
|
|
}
|
|
const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode);
|
|
const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context);
|
|
const mappedTypeNode = createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode);
|
|
context.approximateLength += 10;
|
|
return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine);
|
|
}
|
|
|
|
function createAnonymousTypeNode(type: ObjectType): TypeNode {
|
|
const typeId = "" + type.id;
|
|
const symbol = type.symbol;
|
|
if (symbol) {
|
|
if (isJSConstructor(symbol.valueDeclaration)) {
|
|
// Instance and static types share the same symbol; only add 'typeof' for the static side.
|
|
const isInstanceType = type === getDeclaredTypeOfClassOrInterface(symbol) ? SymbolFlags.Type : SymbolFlags.Value;
|
|
return symbolToTypeNode(symbol, context, isInstanceType);
|
|
}
|
|
// Always use 'typeof T' for type of class, enum, and module objects
|
|
else if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) && !(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) ||
|
|
symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) ||
|
|
shouldWriteTypeOfFunctionSymbol()) {
|
|
return symbolToTypeNode(symbol, context, SymbolFlags.Value);
|
|
}
|
|
else if (context.visitedTypes && context.visitedTypes.has(typeId)) {
|
|
// If type is an anonymous type literal in a type alias declaration, use type alias name
|
|
const typeAlias = getTypeAliasForTypeLiteral(type);
|
|
if (typeAlias) {
|
|
// The specified symbol flags need to be reinterpreted as type flags
|
|
return symbolToTypeNode(typeAlias, context, SymbolFlags.Type);
|
|
}
|
|
else {
|
|
return createElidedInformationPlaceholder(context);
|
|
}
|
|
}
|
|
else {
|
|
return visitAndTransformType(type, createTypeNodeFromObjectType);
|
|
}
|
|
}
|
|
else {
|
|
// Anonymous types without a symbol are never circular.
|
|
return createTypeNodeFromObjectType(type);
|
|
}
|
|
function shouldWriteTypeOfFunctionSymbol() {
|
|
const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method
|
|
some(symbol.declarations, declaration => hasModifier(declaration, ModifierFlags.Static));
|
|
const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) &&
|
|
(symbol.parent || // is exported function symbol
|
|
forEach(symbol.declarations, declaration =>
|
|
declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock));
|
|
if (isStaticMethodSymbol || isNonLocalFunctionSymbol) {
|
|
// typeof is allowed only for static/non local functions
|
|
return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes && context.visitedTypes.has(typeId))) && // it is type of the symbol uses itself recursively
|
|
(!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed
|
|
}
|
|
}
|
|
}
|
|
|
|
function visitAndTransformType<T>(type: Type, transform: (type: Type) => T) {
|
|
const typeId = "" + type.id;
|
|
const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class;
|
|
const id = getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).node ? "N" + getNodeId((<TypeReference>type).node!) :
|
|
type.symbol ? (isConstructorObject ? "+" : "") + getSymbolId(type.symbol) :
|
|
undefined;
|
|
// Since instantiations of the same anonymous type have the same symbol, tracking symbols instead
|
|
// of types allows us to catch circular references to instantiations of the same anonymous type
|
|
if (!context.visitedTypes) {
|
|
context.visitedTypes = createMap<true>();
|
|
}
|
|
if (id && !context.symbolDepth) {
|
|
context.symbolDepth = createMap<number>();
|
|
}
|
|
|
|
let depth: number | undefined;
|
|
if (id) {
|
|
depth = context.symbolDepth!.get(id) || 0;
|
|
if (depth > 10) {
|
|
return createElidedInformationPlaceholder(context);
|
|
}
|
|
context.symbolDepth!.set(id, depth + 1);
|
|
}
|
|
context.visitedTypes.set(typeId, true);
|
|
const result = transform(type);
|
|
context.visitedTypes.delete(typeId);
|
|
if (id) {
|
|
context.symbolDepth!.set(id, depth!);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function createTypeNodeFromObjectType(type: ObjectType): TypeNode {
|
|
if (isGenericMappedType(type)) {
|
|
return createMappedTypeNodeFromType(type);
|
|
}
|
|
|
|
const resolved = resolveStructuredTypeMembers(type);
|
|
if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) {
|
|
if (!resolved.callSignatures.length && !resolved.constructSignatures.length) {
|
|
context.approximateLength += 2;
|
|
return setEmitFlags(createTypeLiteralNode(/*members*/ undefined), EmitFlags.SingleLine);
|
|
}
|
|
|
|
if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) {
|
|
const signature = resolved.callSignatures[0];
|
|
const signatureNode = <FunctionTypeNode>signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context);
|
|
return signatureNode;
|
|
|
|
}
|
|
|
|
if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) {
|
|
const signature = resolved.constructSignatures[0];
|
|
const signatureNode = <ConstructorTypeNode>signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context);
|
|
return signatureNode;
|
|
}
|
|
}
|
|
|
|
const savedFlags = context.flags;
|
|
context.flags |= NodeBuilderFlags.InObjectTypeLiteral;
|
|
const members = createTypeNodesFromResolvedType(resolved);
|
|
context.flags = savedFlags;
|
|
const typeLiteralNode = createTypeLiteralNode(members);
|
|
context.approximateLength += 2;
|
|
return setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine);
|
|
}
|
|
|
|
function typeReferenceToTypeNode(type: TypeReference) {
|
|
const typeArguments: readonly Type[] = getTypeArguments(type);
|
|
if (type.target === globalArrayType || type.target === globalReadonlyArrayType) {
|
|
if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) {
|
|
const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context);
|
|
return createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]);
|
|
}
|
|
const elementType = typeToTypeNodeHelper(typeArguments[0], context);
|
|
const arrayType = createArrayTypeNode(elementType);
|
|
return type.target === globalArrayType ? arrayType : createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType);
|
|
}
|
|
else if (type.target.objectFlags & ObjectFlags.Tuple) {
|
|
if (typeArguments.length > 0) {
|
|
const arity = getTypeReferenceArity(type);
|
|
const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context);
|
|
const hasRestElement = (<TupleType>type.target).hasRestElement;
|
|
if (tupleConstituentNodes) {
|
|
for (let i = (<TupleType>type.target).minLength; i < Math.min(arity, tupleConstituentNodes.length); i++) {
|
|
tupleConstituentNodes[i] = hasRestElement && i === arity - 1 ?
|
|
createRestTypeNode(createArrayTypeNode(tupleConstituentNodes[i])) :
|
|
createOptionalTypeNode(tupleConstituentNodes[i]);
|
|
}
|
|
const tupleTypeNode = createTupleTypeNode(tupleConstituentNodes);
|
|
return (<TupleType>type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode;
|
|
}
|
|
}
|
|
if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) {
|
|
const tupleTypeNode = createTupleTypeNode([]);
|
|
return (<TupleType>type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode;
|
|
}
|
|
context.encounteredError = true;
|
|
return undefined!; // TODO: GH#18217
|
|
}
|
|
else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral &&
|
|
type.symbol.valueDeclaration &&
|
|
isClassLike(type.symbol.valueDeclaration) &&
|
|
!isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)
|
|
) {
|
|
return createAnonymousTypeNode(type);
|
|
}
|
|
else {
|
|
const outerTypeParameters = type.target.outerTypeParameters;
|
|
let i = 0;
|
|
let resultType: TypeReferenceNode | ImportTypeNode | undefined;
|
|
if (outerTypeParameters) {
|
|
const length = outerTypeParameters.length;
|
|
while (i < length) {
|
|
// Find group of type arguments for type parameters with the same declaring container.
|
|
const start = i;
|
|
const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i])!;
|
|
do {
|
|
i++;
|
|
} while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent);
|
|
// When type parameters are their own type arguments for the whole group (i.e. we have
|
|
// the default outer type arguments), we don't show the group.
|
|
if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) {
|
|
const typeArgumentSlice = mapToTypeNodes(typeArguments.slice(start, i), context);
|
|
const flags = context.flags;
|
|
context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences;
|
|
const ref = symbolToTypeNode(parent, context, SymbolFlags.Type, typeArgumentSlice) as TypeReferenceNode | ImportTypeNode;
|
|
context.flags = flags;
|
|
resultType = !resultType ? ref : appendReferenceToType(resultType, ref as TypeReferenceNode);
|
|
}
|
|
}
|
|
}
|
|
let typeArgumentNodes: readonly TypeNode[] | undefined;
|
|
if (typeArguments.length > 0) {
|
|
const typeParameterCount = (type.target.typeParameters || emptyArray).length;
|
|
typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context);
|
|
}
|
|
const flags = context.flags;
|
|
context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences;
|
|
const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes);
|
|
context.flags = flags;
|
|
return !resultType ? finalRef : appendReferenceToType(resultType, finalRef as TypeReferenceNode);
|
|
}
|
|
}
|
|
|
|
|
|
function appendReferenceToType(root: TypeReferenceNode | ImportTypeNode, ref: TypeReferenceNode): TypeReferenceNode | ImportTypeNode {
|
|
if (isImportTypeNode(root)) {
|
|
// first shift type arguments
|
|
const innerParams = root.typeArguments;
|
|
if (root.qualifier) {
|
|
(isIdentifier(root.qualifier) ? root.qualifier : root.qualifier.right).typeArguments = innerParams;
|
|
}
|
|
root.typeArguments = ref.typeArguments;
|
|
// then move qualifiers
|
|
const ids = getAccessStack(ref);
|
|
for (const id of ids) {
|
|
root.qualifier = root.qualifier ? createQualifiedName(root.qualifier, id) : id;
|
|
}
|
|
return root;
|
|
}
|
|
else {
|
|
// first shift type arguments
|
|
const innerParams = root.typeArguments;
|
|
(isIdentifier(root.typeName) ? root.typeName : root.typeName.right).typeArguments = innerParams;
|
|
root.typeArguments = ref.typeArguments;
|
|
// then move qualifiers
|
|
const ids = getAccessStack(ref);
|
|
for (const id of ids) {
|
|
root.typeName = createQualifiedName(root.typeName, id);
|
|
}
|
|
return root;
|
|
}
|
|
}
|
|
|
|
function getAccessStack(ref: TypeReferenceNode): Identifier[] {
|
|
let state = ref.typeName;
|
|
const ids = [];
|
|
while (!isIdentifier(state)) {
|
|
ids.unshift(state.right);
|
|
state = state.left;
|
|
}
|
|
ids.unshift(state);
|
|
return ids;
|
|
}
|
|
|
|
function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined {
|
|
if (checkTruncationLength(context)) {
|
|
return [createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)];
|
|
}
|
|
const typeElements: TypeElement[] = [];
|
|
for (const signature of resolvedType.callSignatures) {
|
|
typeElements.push(<CallSignatureDeclaration>signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context));
|
|
}
|
|
for (const signature of resolvedType.constructSignatures) {
|
|
typeElements.push(<ConstructSignatureDeclaration>signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context));
|
|
}
|
|
if (resolvedType.stringIndexInfo) {
|
|
let indexSignature: IndexSignatureDeclaration;
|
|
if (resolvedType.objectFlags & ObjectFlags.ReverseMapped) {
|
|
indexSignature = indexInfoToIndexSignatureDeclarationHelper(createIndexInfo(anyType, resolvedType.stringIndexInfo.isReadonly, resolvedType.stringIndexInfo.declaration), IndexKind.String, context);
|
|
indexSignature.type = createElidedInformationPlaceholder(context);
|
|
}
|
|
else {
|
|
indexSignature = indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, context);
|
|
}
|
|
typeElements.push(indexSignature);
|
|
}
|
|
if (resolvedType.numberIndexInfo) {
|
|
typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, context));
|
|
}
|
|
|
|
const properties = resolvedType.properties;
|
|
if (!properties) {
|
|
return typeElements;
|
|
}
|
|
|
|
let i = 0;
|
|
for (const propertySymbol of properties) {
|
|
i++;
|
|
if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) {
|
|
if (propertySymbol.flags & SymbolFlags.Prototype) {
|
|
continue;
|
|
}
|
|
if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) {
|
|
context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName));
|
|
}
|
|
}
|
|
if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) {
|
|
typeElements.push(createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined));
|
|
addPropertyToElementList(properties[properties.length - 1], context, typeElements);
|
|
break;
|
|
}
|
|
addPropertyToElementList(propertySymbol, context, typeElements);
|
|
|
|
}
|
|
return typeElements.length ? typeElements : undefined;
|
|
}
|
|
}
|
|
|
|
function createElidedInformationPlaceholder(context: NodeBuilderContext) {
|
|
context.approximateLength += 3;
|
|
if (!(context.flags & NodeBuilderFlags.NoTruncation)) {
|
|
return createTypeReferenceNode(createIdentifier("..."), /*typeArguments*/ undefined);
|
|
}
|
|
return createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
|
|
function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) {
|
|
const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped);
|
|
const propertyType = propertyIsReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ?
|
|
anyType : getTypeOfSymbol(propertySymbol);
|
|
const saveEnclosingDeclaration = context.enclosingDeclaration;
|
|
context.enclosingDeclaration = undefined;
|
|
if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late) {
|
|
const decl = first(propertySymbol.declarations);
|
|
if (hasLateBindableName(decl)) {
|
|
if (isBinaryExpression(decl)) {
|
|
const name = getNameOfDeclaration(decl);
|
|
if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) {
|
|
trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context);
|
|
}
|
|
}
|
|
else {
|
|
trackComputedName(decl.name.expression, saveEnclosingDeclaration, context);
|
|
}
|
|
}
|
|
}
|
|
context.enclosingDeclaration = saveEnclosingDeclaration;
|
|
const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context);
|
|
context.approximateLength += (symbolName(propertySymbol).length + 1);
|
|
const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined;
|
|
if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) {
|
|
const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call);
|
|
for (const signature of signatures) {
|
|
const methodDeclaration = <MethodSignature>signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context);
|
|
methodDeclaration.name = propertyName;
|
|
methodDeclaration.questionToken = optionalToken;
|
|
typeElements.push(preserveCommentsOn(methodDeclaration));
|
|
}
|
|
}
|
|
else {
|
|
const savedFlags = context.flags;
|
|
context.flags |= propertyIsReverseMapped ? NodeBuilderFlags.InReverseMappedType : 0;
|
|
let propertyTypeNode: TypeNode;
|
|
if (propertyIsReverseMapped && !!(savedFlags & NodeBuilderFlags.InReverseMappedType)) {
|
|
propertyTypeNode = createElidedInformationPlaceholder(context);
|
|
}
|
|
else {
|
|
propertyTypeNode = propertyType ? typeToTypeNodeHelper(propertyType, context) : createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
context.flags = savedFlags;
|
|
|
|
const modifiers = isReadonlySymbol(propertySymbol) ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined;
|
|
if (modifiers) {
|
|
context.approximateLength += 9;
|
|
}
|
|
const propertySignature = createPropertySignature(
|
|
modifiers,
|
|
propertyName,
|
|
optionalToken,
|
|
propertyTypeNode,
|
|
/*initializer*/ undefined);
|
|
|
|
typeElements.push(preserveCommentsOn(propertySignature));
|
|
}
|
|
|
|
function preserveCommentsOn<T extends Node>(node: T) {
|
|
if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) {
|
|
const d = find(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag;
|
|
const commentText = d.comment;
|
|
if (commentText) {
|
|
setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]);
|
|
}
|
|
}
|
|
else if (propertySymbol.valueDeclaration) {
|
|
// Copy comments to node for declaration emit
|
|
setCommentRange(node, propertySymbol.valueDeclaration);
|
|
}
|
|
return node;
|
|
}
|
|
}
|
|
|
|
function mapToTypeNodes(types: readonly Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined {
|
|
if (some(types)) {
|
|
if (checkTruncationLength(context)) {
|
|
if (!isBareList) {
|
|
return [createTypeReferenceNode("...", /*typeArguments*/ undefined)];
|
|
}
|
|
else if (types.length > 2) {
|
|
return [
|
|
typeToTypeNodeHelper(types[0], context),
|
|
createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined),
|
|
typeToTypeNodeHelper(types[types.length - 1], context)
|
|
];
|
|
}
|
|
}
|
|
const result = [];
|
|
let i = 0;
|
|
for (const type of types) {
|
|
i++;
|
|
if (checkTruncationLength(context) && (i + 2 < types.length - 1)) {
|
|
result.push(createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined));
|
|
const typeNode = typeToTypeNodeHelper(types[types.length - 1], context);
|
|
if (typeNode) {
|
|
result.push(typeNode);
|
|
}
|
|
break;
|
|
}
|
|
context.approximateLength += 2; // Account for whitespace + separator
|
|
const typeNode = typeToTypeNodeHelper(type, context);
|
|
if (typeNode) {
|
|
result.push(typeNode);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, context: NodeBuilderContext): IndexSignatureDeclaration {
|
|
const name = getNameFromIndexInfo(indexInfo) || "x";
|
|
const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword);
|
|
|
|
const indexingParameter = createParameter(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
/*dotDotDotToken*/ undefined,
|
|
name,
|
|
/*questionToken*/ undefined,
|
|
indexerTypeNode,
|
|
/*initializer*/ undefined);
|
|
const typeNode = typeToTypeNodeHelper(indexInfo.type || anyType, context);
|
|
if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) {
|
|
context.encounteredError = true;
|
|
}
|
|
context.approximateLength += (name.length + 4);
|
|
return createIndexSignature(
|
|
/*decorators*/ undefined,
|
|
indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined,
|
|
[indexingParameter],
|
|
typeNode);
|
|
}
|
|
|
|
function signatureToSignatureDeclarationHelper(signature: Signature, kind: SyntaxKind, context: NodeBuilderContext): SignatureDeclaration {
|
|
const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType;
|
|
if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s
|
|
let typeParameters: TypeParameterDeclaration[] | undefined;
|
|
let typeArguments: TypeNode[] | undefined;
|
|
if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) {
|
|
typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context));
|
|
}
|
|
else {
|
|
typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context));
|
|
}
|
|
|
|
const parameters = getExpandedParameters(signature).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor));
|
|
if (signature.thisParameter) {
|
|
const thisParameter = symbolToParameterDeclaration(signature.thisParameter, context);
|
|
parameters.unshift(thisParameter);
|
|
}
|
|
|
|
let returnTypeNode: TypeNode | undefined;
|
|
const typePredicate = getTypePredicateOfSignature(signature);
|
|
if (typePredicate) {
|
|
const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
|
|
createToken(SyntaxKind.AssertsKeyword) :
|
|
undefined;
|
|
const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ?
|
|
setEmitFlags(createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) :
|
|
createThisTypeNode();
|
|
const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context);
|
|
returnTypeNode = createTypePredicateNodeWithModifier(assertsModifier, parameterName, typeNode);
|
|
}
|
|
else {
|
|
const returnType = getReturnTypeOfSignature(signature);
|
|
if (returnType && !(suppressAny && isTypeAny(returnType))) {
|
|
returnTypeNode = typeToTypeNodeHelper(returnType, context);
|
|
}
|
|
else if (!suppressAny) {
|
|
returnTypeNode = createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
}
|
|
context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum
|
|
return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode, typeArguments);
|
|
}
|
|
|
|
function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration {
|
|
const savedContextFlags = context.flags;
|
|
context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic
|
|
const name = typeParameterToName(type, context);
|
|
const defaultParameter = getDefaultFromTypeParameter(type);
|
|
const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context);
|
|
context.flags = savedContextFlags;
|
|
return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode);
|
|
}
|
|
|
|
function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration {
|
|
const constraintNode = constraint && typeToTypeNodeHelper(constraint, context);
|
|
return typeParameterToDeclarationWithConstraint(type, context, constraintNode);
|
|
}
|
|
|
|
function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration {
|
|
let parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind<ParameterDeclaration>(parameterSymbol, SyntaxKind.Parameter);
|
|
if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) {
|
|
parameterDeclaration = getDeclarationOfKind<JSDocParameterTag>(parameterSymbol, SyntaxKind.JSDocParameterTag);
|
|
}
|
|
|
|
let parameterType = getTypeOfSymbol(parameterSymbol);
|
|
if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) {
|
|
parameterType = getOptionalType(parameterType);
|
|
}
|
|
const parameterTypeNode = typeToTypeNodeHelper(parameterType, context);
|
|
|
|
const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && parameterDeclaration.modifiers ? parameterDeclaration.modifiers.map(getSynthesizedClone) : undefined;
|
|
const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter;
|
|
const dotDotDotToken = isRest ? createToken(SyntaxKind.DotDotDotToken) : undefined;
|
|
const name = parameterDeclaration ? parameterDeclaration.name ?
|
|
parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) :
|
|
parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) :
|
|
cloneBindingName(parameterDeclaration.name) :
|
|
symbolName(parameterSymbol) :
|
|
symbolName(parameterSymbol);
|
|
const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter;
|
|
const questionToken = isOptional ? createToken(SyntaxKind.QuestionToken) : undefined;
|
|
const parameterNode = createParameter(
|
|
/*decorators*/ undefined,
|
|
modifiers,
|
|
dotDotDotToken,
|
|
name,
|
|
questionToken,
|
|
parameterTypeNode,
|
|
/*initializer*/ undefined);
|
|
context.approximateLength += symbolName(parameterSymbol).length + 3;
|
|
return parameterNode;
|
|
|
|
function cloneBindingName(node: BindingName): BindingName {
|
|
return <BindingName>elideInitializerAndSetEmitFlags(node);
|
|
function elideInitializerAndSetEmitFlags(node: Node): Node {
|
|
if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) {
|
|
trackComputedName(node.expression, context.enclosingDeclaration, context);
|
|
}
|
|
const visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!;
|
|
const clone = nodeIsSynthesized(visited) ? visited : getSynthesizedClone(visited);
|
|
if (clone.kind === SyntaxKind.BindingElement) {
|
|
(<BindingElement>clone).initializer = undefined;
|
|
}
|
|
return setEmitFlags(clone, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping);
|
|
}
|
|
}
|
|
}
|
|
|
|
function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) {
|
|
if (!context.tracker.trackSymbol) return;
|
|
// get symbol of the first identifier of the entityName
|
|
const firstIdentifier = getFirstIdentifier(accessExpression);
|
|
const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
|
|
if (name) {
|
|
context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value);
|
|
}
|
|
}
|
|
|
|
function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) {
|
|
context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217
|
|
return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol);
|
|
}
|
|
|
|
function lookupSymbolChainWorker(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) {
|
|
// Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration.
|
|
let chain: Symbol[];
|
|
const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter;
|
|
if (!isTypeParameter && (context.enclosingDeclaration || context.flags & NodeBuilderFlags.UseFullyQualifiedType) && !(context.flags & NodeBuilderFlags.DoNotIncludeSymbolChain)) {
|
|
chain = Debug.checkDefined(getSymbolChain(symbol, meaning, /*endOfChain*/ true));
|
|
Debug.assert(chain && chain.length > 0);
|
|
}
|
|
else {
|
|
chain = [symbol];
|
|
}
|
|
return chain;
|
|
|
|
/** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */
|
|
function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined {
|
|
let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing));
|
|
let parentSpecifiers: (string | undefined)[];
|
|
if (!accessibleSymbolChain ||
|
|
needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) {
|
|
|
|
// Go up and add our parent.
|
|
const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration);
|
|
if (length(parents)) {
|
|
parentSpecifiers = parents!.map(symbol =>
|
|
some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)
|
|
? getSpecifierForModuleSymbol(symbol, context)
|
|
: undefined);
|
|
const indices = parents!.map((_, i) => i);
|
|
indices.sort(sortByBestName);
|
|
const sortedParents = indices.map(i => parents![i]);
|
|
for (const parent of sortedParents) {
|
|
const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false);
|
|
if (parentChain) {
|
|
if (parent.exports && parent.exports.get(InternalSymbolName.ExportEquals) &&
|
|
getSymbolIfSameReference(parent.exports.get(InternalSymbolName.ExportEquals)!, symbol)) {
|
|
// parentChain root _is_ symbol - symbol is a module export=, so it kinda looks like it's own parent
|
|
// No need to lookup an alias for the symbol in itself
|
|
accessibleSymbolChain = parentChain;
|
|
break;
|
|
}
|
|
accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (accessibleSymbolChain) {
|
|
return accessibleSymbolChain;
|
|
}
|
|
if (
|
|
// If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols.
|
|
endOfChain ||
|
|
// If a parent symbol is an anonymous type, don't write it.
|
|
!(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) {
|
|
// If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.)
|
|
if (!endOfChain && !yieldModuleSymbol && !!forEach(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) {
|
|
return;
|
|
}
|
|
return [symbol];
|
|
}
|
|
|
|
function sortByBestName(a: number, b: number) {
|
|
const specifierA = parentSpecifiers[a];
|
|
const specifierB = parentSpecifiers[b];
|
|
if (specifierA && specifierB) {
|
|
const isBRelative = pathIsRelative(specifierB);
|
|
if (pathIsRelative(specifierA) === isBRelative) {
|
|
// Both relative or both non-relative, sort by number of parts
|
|
return moduleSpecifiers.countPathComponents(specifierA) - moduleSpecifiers.countPathComponents(specifierB);
|
|
}
|
|
if (isBRelative) {
|
|
// A is non-relative, B is relative: prefer A
|
|
return -1;
|
|
}
|
|
// A is relative, B is non-relative: prefer B
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
function typeParametersToTypeParameterDeclarations(symbol: Symbol, context: NodeBuilderContext) {
|
|
let typeParameterNodes: NodeArray<TypeParameterDeclaration> | undefined;
|
|
const targetSymbol = getTargetSymbol(symbol);
|
|
if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) {
|
|
typeParameterNodes = createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context)));
|
|
}
|
|
return typeParameterNodes;
|
|
}
|
|
|
|
function lookupTypeParameterNodes(chain: Symbol[], index: number, context: NodeBuilderContext) {
|
|
Debug.assert(chain && 0 <= index && index < chain.length);
|
|
const symbol = chain[index];
|
|
const symbolId = "" + getSymbolId(symbol);
|
|
if (context.typeParameterSymbolList && context.typeParameterSymbolList.get(symbolId)) {
|
|
return undefined;
|
|
}
|
|
(context.typeParameterSymbolList || (context.typeParameterSymbolList = createMap())).set(symbolId, true);
|
|
let typeParameterNodes: readonly TypeNode[] | readonly TypeParameterDeclaration[] | undefined;
|
|
if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) {
|
|
const parentSymbol = symbol;
|
|
const nextSymbol = chain[index + 1];
|
|
if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) {
|
|
const params = getTypeParametersOfClassOrInterface(
|
|
parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol
|
|
);
|
|
typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).mapper!)), context);
|
|
}
|
|
else {
|
|
typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context);
|
|
}
|
|
}
|
|
return typeParameterNodes;
|
|
}
|
|
|
|
/**
|
|
* Given A[B][C][D], finds A[B]
|
|
*/
|
|
function getTopmostIndexedAccessType(top: IndexedAccessTypeNode): IndexedAccessTypeNode {
|
|
if (isIndexedAccessTypeNode(top.objectType)) {
|
|
return getTopmostIndexedAccessType(top.objectType);
|
|
}
|
|
return top;
|
|
}
|
|
|
|
function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext) {
|
|
const file = getDeclarationOfKind<SourceFile>(symbol, SyntaxKind.SourceFile);
|
|
if (file && file.moduleName !== undefined) {
|
|
// Use the amd name if it is available
|
|
return file.moduleName;
|
|
}
|
|
if (!file) {
|
|
if (context.tracker.trackReferencedAmbientModule) {
|
|
const ambientDecls = filter(symbol.declarations, isAmbientModule);
|
|
if (length(ambientDecls)) {
|
|
for (const decl of ambientDecls) {
|
|
context.tracker.trackReferencedAmbientModule(decl, symbol);
|
|
}
|
|
}
|
|
}
|
|
if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) {
|
|
return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1);
|
|
}
|
|
}
|
|
if (!context.enclosingDeclaration || !context.tracker.moduleResolverHost) {
|
|
// If there's no context declaration, we can't lookup a non-ambient specifier, so we just use the symbol name
|
|
if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) {
|
|
return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1);
|
|
}
|
|
return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full
|
|
}
|
|
const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration));
|
|
const links = getSymbolLinks(symbol);
|
|
let specifier = links.specifierCache && links.specifierCache.get(contextFile.path);
|
|
if (!specifier) {
|
|
const isBundle = (compilerOptions.out || compilerOptions.outFile);
|
|
// For declaration bundles, we need to generate absolute paths relative to the common source dir for imports,
|
|
// just like how the declaration emitter does for the ambient module declarations - we can easily accomplish this
|
|
// using the `baseUrl` compiler option (which we would otherwise never use in declaration emit) and a non-relative
|
|
// specifier preference
|
|
const { moduleResolverHost } = context.tracker;
|
|
const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions;
|
|
specifier = first(moduleSpecifiers.getModuleSpecifiers(
|
|
symbol,
|
|
specifierCompilerOptions,
|
|
contextFile,
|
|
moduleResolverHost,
|
|
host.getSourceFiles(),
|
|
{ importModuleSpecifierPreference: isBundle ? "non-relative" : "relative" },
|
|
host.redirectTargetsMap,
|
|
));
|
|
links.specifierCache = links.specifierCache || createMap();
|
|
links.specifierCache.set(contextFile.path, specifier);
|
|
}
|
|
return specifier;
|
|
}
|
|
|
|
function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode {
|
|
const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module
|
|
|
|
const isTypeOf = meaning === SymbolFlags.Value;
|
|
if (some(chain[0].declarations, hasNonGlobalAugmentationExternalModuleSymbol)) {
|
|
// module is root, must use `ImportTypeNode`
|
|
const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined;
|
|
const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context);
|
|
const specifier = getSpecifierForModuleSymbol(chain[0], context);
|
|
if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && specifier.indexOf("/node_modules/") >= 0) {
|
|
// If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error
|
|
// since declaration files with these kinds of references are liable to fail when published :(
|
|
context.encounteredError = true;
|
|
if (context.tracker.reportLikelyUnsafeImportRequiredError) {
|
|
context.tracker.reportLikelyUnsafeImportRequiredError(specifier);
|
|
}
|
|
}
|
|
const lit = createLiteralTypeNode(createLiteral(specifier));
|
|
if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]);
|
|
context.approximateLength += specifier.length + 10; // specifier + import("")
|
|
if (!nonRootParts || isEntityName(nonRootParts)) {
|
|
if (nonRootParts) {
|
|
const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right;
|
|
lastId.typeArguments = undefined;
|
|
}
|
|
return createImportTypeNode(lit, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf);
|
|
}
|
|
else {
|
|
const splitNode = getTopmostIndexedAccessType(nonRootParts);
|
|
const qualifier = (splitNode.objectType as TypeReferenceNode).typeName;
|
|
return createIndexedAccessTypeNode(createImportTypeNode(lit, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType);
|
|
}
|
|
}
|
|
|
|
const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0);
|
|
if (isIndexedAccessTypeNode(entityName)) {
|
|
return entityName; // Indexed accesses can never be `typeof`
|
|
}
|
|
if (isTypeOf) {
|
|
return createTypeQueryNode(entityName);
|
|
}
|
|
else {
|
|
const lastId = isIdentifier(entityName) ? entityName : entityName.right;
|
|
const lastTypeArgs = lastId.typeArguments;
|
|
lastId.typeArguments = undefined;
|
|
return createTypeReferenceNode(entityName, lastTypeArgs as NodeArray<TypeNode>);
|
|
}
|
|
|
|
function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode {
|
|
const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context);
|
|
const symbol = chain[index];
|
|
|
|
const parent = chain[index - 1];
|
|
let symbolName: string | undefined;
|
|
if (index === 0) {
|
|
context.flags |= NodeBuilderFlags.InInitialEntityName;
|
|
symbolName = getNameOfSymbolAsWritten(symbol, context);
|
|
context.approximateLength += (symbolName ? symbolName.length : 0) + 1;
|
|
context.flags ^= NodeBuilderFlags.InInitialEntityName;
|
|
}
|
|
else {
|
|
if (parent && getExportsOfSymbol(parent)) {
|
|
const exports = getExportsOfSymbol(parent);
|
|
forEachEntry(exports, (ex, name) => {
|
|
if (getSymbolIfSameReference(ex, symbol) && !isLateBoundName(name) && name !== InternalSymbolName.ExportEquals) {
|
|
symbolName = unescapeLeadingUnderscores(name);
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if (!symbolName) {
|
|
symbolName = getNameOfSymbolAsWritten(symbol, context);
|
|
}
|
|
context.approximateLength += symbolName.length + 1;
|
|
|
|
if (!(context.flags & NodeBuilderFlags.ForbidIndexedAccessSymbolReferences) && parent &&
|
|
getMembersOfSymbol(parent) && getMembersOfSymbol(parent).get(symbol.escapedName) &&
|
|
getSymbolIfSameReference(getMembersOfSymbol(parent).get(symbol.escapedName)!, symbol)) {
|
|
// Should use an indexed access
|
|
const LHS = createAccessFromSymbolChain(chain, index - 1, stopper);
|
|
if (isIndexedAccessTypeNode(LHS)) {
|
|
return createIndexedAccessTypeNode(LHS, createLiteralTypeNode(createLiteral(symbolName)));
|
|
}
|
|
else {
|
|
return createIndexedAccessTypeNode(createTypeReferenceNode(LHS, typeParameterNodes as readonly TypeNode[]), createLiteralTypeNode(createLiteral(symbolName)));
|
|
}
|
|
}
|
|
|
|
const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping);
|
|
identifier.symbol = symbol;
|
|
|
|
if (index > stopper) {
|
|
const LHS = createAccessFromSymbolChain(chain, index - 1, stopper);
|
|
if (!isEntityName(LHS)) {
|
|
return Debug.fail("Impossible construct - an export of an indexed access cannot be reachable");
|
|
}
|
|
return createQualifiedName(LHS, identifier);
|
|
}
|
|
return identifier;
|
|
}
|
|
}
|
|
|
|
function typeParameterShadowsNameInScope(escapedName: __String, context: NodeBuilderContext) {
|
|
return !!resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, escapedName, /*isUse*/ false);
|
|
}
|
|
|
|
function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) {
|
|
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) {
|
|
const cached = context.typeParameterNames.get("" + getTypeId(type));
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
}
|
|
let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true);
|
|
if (!(result.kind & SyntaxKind.Identifier)) {
|
|
return createIdentifier("(Missing type parameter)");
|
|
}
|
|
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
|
|
const rawtext = result.escapedText as string;
|
|
let i = 0;
|
|
let text = rawtext;
|
|
while ((context.typeParameterNamesByText && context.typeParameterNamesByText.get(text)) || typeParameterShadowsNameInScope(text as __String, context)) {
|
|
i++;
|
|
text = `${rawtext}_${i}`;
|
|
}
|
|
if (text !== rawtext) {
|
|
result = createIdentifier(text, result.typeArguments);
|
|
}
|
|
(context.typeParameterNames || (context.typeParameterNames = createMap())).set("" + getTypeId(type), result);
|
|
(context.typeParameterNamesByText || (context.typeParameterNamesByText = createMap())).set(result.escapedText as string, true);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier;
|
|
function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName;
|
|
function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName {
|
|
const chain = lookupSymbolChain(symbol, context, meaning);
|
|
|
|
if (expectsIdentifier && chain.length !== 1
|
|
&& !context.encounteredError
|
|
&& !(context.flags & NodeBuilderFlags.AllowQualifedNameInPlaceOfIdentifier)) {
|
|
context.encounteredError = true;
|
|
}
|
|
return createEntityNameFromSymbolChain(chain, chain.length - 1);
|
|
|
|
function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName {
|
|
const typeParameterNodes = lookupTypeParameterNodes(chain, index, context);
|
|
const symbol = chain[index];
|
|
|
|
if (index === 0) {
|
|
context.flags |= NodeBuilderFlags.InInitialEntityName;
|
|
}
|
|
const symbolName = getNameOfSymbolAsWritten(symbol, context);
|
|
if (index === 0) {
|
|
context.flags ^= NodeBuilderFlags.InInitialEntityName;
|
|
}
|
|
|
|
const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping);
|
|
identifier.symbol = symbol;
|
|
|
|
return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier;
|
|
}
|
|
}
|
|
|
|
function symbolToExpression(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) {
|
|
const chain = lookupSymbolChain(symbol, context, meaning);
|
|
|
|
return createExpressionFromSymbolChain(chain, chain.length - 1);
|
|
|
|
function createExpressionFromSymbolChain(chain: Symbol[], index: number): Expression {
|
|
const typeParameterNodes = lookupTypeParameterNodes(chain, index, context);
|
|
const symbol = chain[index];
|
|
|
|
if (index === 0) {
|
|
context.flags |= NodeBuilderFlags.InInitialEntityName;
|
|
}
|
|
let symbolName = getNameOfSymbolAsWritten(symbol, context);
|
|
if (index === 0) {
|
|
context.flags ^= NodeBuilderFlags.InInitialEntityName;
|
|
}
|
|
let firstChar = symbolName.charCodeAt(0);
|
|
|
|
if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) {
|
|
return createLiteral(getSpecifierForModuleSymbol(symbol, context));
|
|
}
|
|
const canUsePropertyAccess = firstChar === CharacterCodes.hash ?
|
|
symbolName.length > 1 && isIdentifierStart(symbolName.charCodeAt(1), languageVersion) :
|
|
isIdentifierStart(firstChar, languageVersion);
|
|
if (index === 0 || canUsePropertyAccess) {
|
|
const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping);
|
|
identifier.symbol = symbol;
|
|
|
|
return index > 0 ? createPropertyAccess(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier;
|
|
}
|
|
else {
|
|
if (firstChar === CharacterCodes.openBracket) {
|
|
symbolName = symbolName.substring(1, symbolName.length - 1);
|
|
firstChar = symbolName.charCodeAt(0);
|
|
}
|
|
let expression: Expression | undefined;
|
|
if (isSingleOrDoubleQuote(firstChar)) {
|
|
expression = createLiteral(symbolName.substring(1, symbolName.length - 1).replace(/\\./g, s => s.substring(1)));
|
|
(expression as StringLiteral).singleQuote = firstChar === CharacterCodes.singleQuote;
|
|
}
|
|
else if (("" + +symbolName) === symbolName) {
|
|
expression = createLiteral(+symbolName);
|
|
}
|
|
if (!expression) {
|
|
expression = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping);
|
|
expression.symbol = symbol;
|
|
}
|
|
return createElementAccess(createExpressionFromSymbolChain(chain, index - 1), expression);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isSingleQuotedStringNamed(d: Declaration) {
|
|
const name = getNameOfDeclaration(d);
|
|
if (name && isStringLiteral(name) && (
|
|
name.singleQuote ||
|
|
(!nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))
|
|
)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) {
|
|
const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed);
|
|
const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote);
|
|
if (fromNameType) {
|
|
return fromNameType;
|
|
}
|
|
if (isKnownSymbol(symbol)) {
|
|
return createComputedPropertyName(createPropertyAccess(createIdentifier("Symbol"), (symbol.escapedName as string).substr(3)));
|
|
}
|
|
const rawName = unescapeLeadingUnderscores(symbol.escapedName);
|
|
return createPropertyNameNodeForIdentifierOrLiteral(rawName, singleQuote);
|
|
}
|
|
|
|
// See getNameForSymbolFromNameType for a stringy equivalent
|
|
function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote?: boolean) {
|
|
const nameType = getSymbolLinks(symbol).nameType;
|
|
if (nameType) {
|
|
if (nameType.flags & TypeFlags.StringOrNumberLiteral) {
|
|
const name = "" + (<StringLiteralType | NumberLiteralType>nameType).value;
|
|
if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) {
|
|
return createLiteral(name, !!singleQuote);
|
|
}
|
|
if (isNumericLiteralName(name) && startsWith(name, "-")) {
|
|
return createComputedPropertyName(createLiteral(+name));
|
|
}
|
|
return createPropertyNameNodeForIdentifierOrLiteral(name);
|
|
}
|
|
if (nameType.flags & TypeFlags.UniqueESSymbol) {
|
|
return createComputedPropertyName(symbolToExpression((<UniqueESSymbolType>nameType).symbol, context, SymbolFlags.Value));
|
|
}
|
|
}
|
|
}
|
|
|
|
function createPropertyNameNodeForIdentifierOrLiteral(name: string, singleQuote?: boolean) {
|
|
return isIdentifierText(name, compilerOptions.target) ? createIdentifier(name) : createLiteral(isNumericLiteralName(name) ? +name : name, !!singleQuote);
|
|
}
|
|
|
|
function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext {
|
|
const initial: NodeBuilderContext = { ...context };
|
|
// Make type parameters created within this context not consume the name outside this context
|
|
// The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when
|
|
// it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends
|
|
// through the type tree, so the only cases where we could have used distinct sibling scopes was when there
|
|
// were multiple generic overloads with similar generated type parameter names
|
|
// The effect:
|
|
// When we write out
|
|
// export const x: <T>(x: T) => T
|
|
// export const y: <T>(x: T) => T
|
|
// we write it out like that, rather than as
|
|
// export const x: <T>(x: T) => T
|
|
// export const y: <T_1>(x: T_1) => T_1
|
|
if (initial.typeParameterNames) {
|
|
initial.typeParameterNames = cloneMap(initial.typeParameterNames);
|
|
}
|
|
if (initial.typeParameterNamesByText) {
|
|
initial.typeParameterNamesByText = cloneMap(initial.typeParameterNamesByText);
|
|
}
|
|
if (initial.typeParameterSymbolList) {
|
|
initial.typeParameterSymbolList = cloneMap(initial.typeParameterSymbolList);
|
|
}
|
|
return initial;
|
|
}
|
|
|
|
function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] {
|
|
const serializePropertySymbolForClass = makeSerializePropertySymbol<ClassElement>(createProperty, SyntaxKind.MethodDeclaration, /*useAcessors*/ true);
|
|
const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol<TypeElement>((_decorators, mods, name, question, type, initializer) => createPropertySignature(mods, name, question, type, initializer), SyntaxKind.MethodSignature, /*useAcessors*/ false);
|
|
|
|
// TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of
|
|
// declaration mapping
|
|
|
|
// We save the enclosing declaration off here so it's not adjusted by well-meaning declaration
|
|
// emit codepaths which want to apply more specific contexts (so we can still refer to the root real declaration
|
|
// we're trying to emit from later on)
|
|
const enclosingDeclaration = context.enclosingDeclaration!;
|
|
let results: Statement[] = [];
|
|
const visitedSymbols: Map<true> = createMap();
|
|
let deferredPrivates: Map<Symbol> | undefined;
|
|
const oldcontext = context;
|
|
context = {
|
|
...oldcontext,
|
|
usedSymbolNames: createMap(),
|
|
remappedSymbolNames: createMap(),
|
|
tracker: {
|
|
...oldcontext.tracker,
|
|
trackSymbol: (sym, decl, meaning) => {
|
|
const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeALiases*/ false);
|
|
if (accessibleResult.accessibility === SymbolAccessibility.Accessible) {
|
|
// Lookup the root symbol of the chain of refs we'll use to access it and serialize it
|
|
const chain = lookupSymbolChainWorker(sym, context, meaning);
|
|
if (!(sym.flags & SymbolFlags.Property)) {
|
|
includePrivateSymbol(chain[0]);
|
|
}
|
|
}
|
|
else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) {
|
|
oldcontext.tracker.trackSymbol(sym, decl, meaning);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
if (oldcontext.usedSymbolNames) {
|
|
oldcontext.usedSymbolNames.forEach((_, name) => {
|
|
context.usedSymbolNames!.set(name, true);
|
|
});
|
|
}
|
|
forEachEntry(symbolTable, (symbol, name) => {
|
|
const baseName = unescapeLeadingUnderscores(name);
|
|
void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames`
|
|
});
|
|
let addingDeclare = !bundled;
|
|
const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals);
|
|
if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) {
|
|
symbolTable = createSymbolTable();
|
|
// Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up)
|
|
symbolTable.set(InternalSymbolName.ExportEquals, exportEquals);
|
|
}
|
|
|
|
visitSymbolTable(symbolTable);
|
|
return mergeRedundantStatements(results);
|
|
|
|
function isIdentifierAndNotUndefined(node: Node | undefined): node is Identifier {
|
|
return !!node && node.kind === SyntaxKind.Identifier;
|
|
}
|
|
|
|
function getNamesOfDeclaration(statement: Statement): Identifier[] {
|
|
if (isVariableStatement(statement)) {
|
|
return filter(map(statement.declarationList.declarations, getNameOfDeclaration), isIdentifierAndNotUndefined);
|
|
}
|
|
return filter([getNameOfDeclaration(statement as DeclarationStatement)], isIdentifierAndNotUndefined);
|
|
}
|
|
|
|
function flattenExportAssignedNamespace(statements: Statement[]) {
|
|
const exportAssignment = find(statements, isExportAssignment);
|
|
const ns = find(statements, isModuleDeclaration);
|
|
if (ns && exportAssignment && exportAssignment.isExportEquals &&
|
|
isIdentifier(exportAssignment.expression) && isIdentifier(ns.name) && idText(ns.name) === idText(exportAssignment.expression) &&
|
|
ns.body && isModuleBlock(ns.body)) {
|
|
// Pass 0: Correct situations where a module has both an `export = ns` and multiple top-level exports by stripping the export modifiers from
|
|
// the top-level exports and exporting them in the targeted ns, as can occur when a js file has both typedefs and `module.export` assignments
|
|
const excessExports = filter(statements, s => !!(getModifierFlags(s) & ModifierFlags.Export));
|
|
if (length(excessExports)) {
|
|
ns.body.statements = createNodeArray([...ns.body.statements, createExportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => createExportSpecifier(/*alias*/ undefined, id))),
|
|
/*moduleSpecifier*/ undefined
|
|
)]);
|
|
}
|
|
|
|
|
|
// Pass 1: Flatten `export namespace _exports {} export = _exports;` so long as the `export=` only points at a single namespace declaration
|
|
if (!find(statements, s => s !== ns && nodeHasName(s, ns.name as Identifier))) {
|
|
results = [];
|
|
forEach(ns.body.statements, s => {
|
|
addResult(s, ModifierFlags.None); // Recalculates the ambient (and export, if applicable from above) flag
|
|
});
|
|
statements = [...filter(statements, s => s !== ns && s !== exportAssignment), ...results];
|
|
}
|
|
}
|
|
return statements;
|
|
}
|
|
|
|
function mergeExportDeclarations(statements: Statement[]) {
|
|
// Pass 2: Combine all `export {}` declarations
|
|
const exports = filter(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[];
|
|
if (length(exports) > 1) {
|
|
const nonExports = filter(statements, d => !isExportDeclaration(d) || !!d.moduleSpecifier || !d.exportClause);
|
|
statements = [...nonExports, createExportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createNamedExports(flatMap(exports, e => cast(e.exportClause, isNamedExports).elements)),
|
|
/*moduleSpecifier*/ undefined
|
|
)];
|
|
}
|
|
// Pass 2b: Also combine all `export {} from "..."` declarations as needed
|
|
const reexports = filter(statements, d => isExportDeclaration(d) && !!d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[];
|
|
if (length(reexports) > 1) {
|
|
const groups = group(reexports, decl => isStringLiteral(decl.moduleSpecifier!) ? ">" + decl.moduleSpecifier.text : ">");
|
|
if (groups.length !== reexports.length) {
|
|
for (const group of groups) {
|
|
if (group.length > 1) {
|
|
// remove group members from statements and then merge group members and add back to statements
|
|
statements = [
|
|
...filter(statements, s => group.indexOf(s as ExportDeclaration) === -1),
|
|
createExportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)),
|
|
group[0].moduleSpecifier
|
|
)
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return statements;
|
|
}
|
|
|
|
function inlineExportModifiers(statements: Statement[]) {
|
|
// Pass 3: Move all `export {}`'s to `export` modifiers where possible
|
|
const exportDecl = find(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause) as ExportDeclaration | undefined;
|
|
if (exportDecl && exportDecl.exportClause && isNamedExports(exportDecl.exportClause)) {
|
|
const replacements = mapDefined(exportDecl.exportClause.elements, e => {
|
|
if (!e.propertyName) {
|
|
// export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it
|
|
const associated = filter(statements, s => nodeHasName(s, e.name));
|
|
if (length(associated) && every(associated, canHaveExportModifier)) {
|
|
forEach(associated, addExportModifier);
|
|
return undefined;
|
|
}
|
|
}
|
|
return e;
|
|
});
|
|
if (!length(replacements)) {
|
|
// all clauses removed, filter the export declaration
|
|
statements = filter(statements, s => s !== exportDecl);
|
|
}
|
|
else {
|
|
// some items filtered, others not - update the export declaration
|
|
// (mutating because why not, we're building a whole new tree here anyway)
|
|
exportDecl.exportClause.elements = createNodeArray(replacements);
|
|
}
|
|
}
|
|
return statements;
|
|
}
|
|
|
|
function mergeRedundantStatements(statements: Statement[]) {
|
|
statements = flattenExportAssignedNamespace(statements);
|
|
statements = mergeExportDeclarations(statements);
|
|
statements = inlineExportModifiers(statements);
|
|
|
|
// Not a cleanup, but as a final step: If there is a mix of `export` and non-`export` declarations, but no `export =` or `export {}` add a `export {};` so
|
|
// declaration privacy is respected.
|
|
if (enclosingDeclaration &&
|
|
((isSourceFile(enclosingDeclaration) && isExternalOrCommonJsModule(enclosingDeclaration)) || isModuleDeclaration(enclosingDeclaration)) &&
|
|
(!some(statements, isExternalModuleIndicator) || (!hasScopeMarker(statements) && some(statements, needsScopeMarker)))) {
|
|
statements.push(createEmptyExports());
|
|
}
|
|
return statements;
|
|
}
|
|
|
|
function canHaveExportModifier(node: Statement) {
|
|
return isEnumDeclaration(node) ||
|
|
isVariableStatement(node) ||
|
|
isFunctionDeclaration(node) ||
|
|
isClassDeclaration(node) ||
|
|
(isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)) ||
|
|
isInterfaceDeclaration(node) ||
|
|
isTypeDeclaration(node);
|
|
}
|
|
|
|
function addExportModifier(statement: Statement) {
|
|
const flags = (getModifierFlags(statement) | ModifierFlags.Export) & ~ModifierFlags.Ambient;
|
|
statement.modifiers = createNodeArray(createModifiersFromModifierFlags(flags));
|
|
statement.modifierFlagsCache = 0;
|
|
}
|
|
|
|
function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) {
|
|
const oldDeferredPrivates = deferredPrivates;
|
|
if (!suppressNewPrivateContext) {
|
|
deferredPrivates = createMap();
|
|
}
|
|
symbolTable.forEach((symbol: Symbol) => {
|
|
serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias);
|
|
});
|
|
if (!suppressNewPrivateContext) {
|
|
// deferredPrivates will be filled up by visiting the symbol table
|
|
// And will continue to iterate as elements are added while visited `deferredPrivates`
|
|
// (As that's how a map iterator is defined to work)
|
|
deferredPrivates!.forEach((symbol: Symbol) => {
|
|
serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias);
|
|
});
|
|
}
|
|
deferredPrivates = oldDeferredPrivates;
|
|
}
|
|
|
|
function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) {
|
|
// cache visited list based on merged symbol, since we want to use the unmerged top-level symbol, but
|
|
// still skip reserializing it if we encounter the merged product later on
|
|
const visitedSym = getMergedSymbol(symbol);
|
|
if (visitedSymbols.has("" + getSymbolId(visitedSym))) {
|
|
return; // Already printed
|
|
}
|
|
visitedSymbols.set("" + getSymbolId(visitedSym), true);
|
|
// Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol
|
|
const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope
|
|
if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) {
|
|
const oldContext = context;
|
|
context = cloneNodeBuilderContext(context);
|
|
const result = serializeSymbolWorker(symbol, isPrivate, propertyAsAlias);
|
|
context = oldContext;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias
|
|
// or a merge of some number of those.
|
|
// An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping
|
|
// each symbol in only one of the representations
|
|
// Also, synthesizing a default export of some kind
|
|
// If it's an alias: emit `export default ref`
|
|
// If it's a property: emit `export default _default` with a `_default` prop
|
|
// If it's a class/interface/function: emit a class/interface/function with a `default` modifier
|
|
// These forms can merge, eg (`export default 12; export default interface A {}`)
|
|
function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) {
|
|
const symbolName = unescapeLeadingUnderscores(symbol.escapedName);
|
|
const isDefault = symbol.escapedName === InternalSymbolName.Default;
|
|
if (isStringANonContextualKeyword(symbolName) && !isDefault) {
|
|
// Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :(
|
|
context.encounteredError = true;
|
|
// TODO: Issue error via symbol tracker?
|
|
return; // If we need to emit a private with a keyword name, we're done for, since something else will try to refer to it by that name
|
|
}
|
|
const needsPostExportDefault = isDefault && !!(
|
|
symbol.flags & SymbolFlags.ExportDoesNotSupportDefaultModifier
|
|
|| (symbol.flags & SymbolFlags.Function && length(getPropertiesOfType(getTypeOfSymbol(symbol))))
|
|
) && !(symbol.flags & SymbolFlags.Alias); // An alias symbol should preclude needing to make an alias ourselves
|
|
if (needsPostExportDefault) {
|
|
isPrivate = true;
|
|
}
|
|
const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0);
|
|
const isConstMergedWithNS = symbol.flags & SymbolFlags.Module &&
|
|
symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) &&
|
|
symbol.escapedName !== InternalSymbolName.ExportEquals;
|
|
const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol);
|
|
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) {
|
|
serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
|
|
}
|
|
if (symbol.flags & SymbolFlags.TypeAlias) {
|
|
serializeTypeAlias(symbol, symbolName, modifierFlags);
|
|
}
|
|
// Need to skip over export= symbols below - json source files get a single `Property` flagged
|
|
// symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is.
|
|
if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property)
|
|
&& symbol.escapedName !== InternalSymbolName.ExportEquals
|
|
&& !(symbol.flags & SymbolFlags.Prototype)
|
|
&& !(symbol.flags & SymbolFlags.Class)
|
|
&& !isConstMergedWithNSPrintableAsSignatureMerge) {
|
|
serializeVariableOrProperty(symbol, symbolName, isPrivate, needsPostExportDefault, propertyAsAlias, modifierFlags);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Enum) {
|
|
serializeEnum(symbol, symbolName, modifierFlags);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Class) {
|
|
if (symbol.flags & SymbolFlags.Property) {
|
|
// Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members,
|
|
// since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property
|
|
// _really_ acts like an Alias, and none of a BlockScopedVariable, Class, or Property. This is the travesty of JS binding today.
|
|
serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
|
|
}
|
|
else {
|
|
serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
|
|
}
|
|
}
|
|
if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) {
|
|
serializeModule(symbol, symbolName, modifierFlags);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Interface) {
|
|
serializeInterface(symbol, symbolName, modifierFlags);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Alias) {
|
|
serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Property && symbol.escapedName === InternalSymbolName.ExportEquals) {
|
|
serializeMaybeAliasAssignment(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.ExportStar) {
|
|
// synthesize export * from "moduleReference"
|
|
// Straightforward - only one thing to do - make an export declaration
|
|
for (const node of symbol.declarations) {
|
|
const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!);
|
|
if (!resolvedModule) continue;
|
|
addResult(createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*exportClause*/ undefined, createLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None);
|
|
}
|
|
}
|
|
if (needsPostExportDefault) {
|
|
addResult(createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportAssignment*/ false, createIdentifier(getInternalSymbolName(symbol, symbolName))), ModifierFlags.None);
|
|
}
|
|
}
|
|
|
|
function includePrivateSymbol(symbol: Symbol) {
|
|
if (some(symbol.declarations, isParameterDeclaration)) return;
|
|
Debug.assertIsDefined(deferredPrivates);
|
|
getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol
|
|
deferredPrivates.set("" + getSymbolId(symbol), symbol);
|
|
}
|
|
|
|
function isExportingScope(enclosingDeclaration: Node) {
|
|
return ((isSourceFile(enclosingDeclaration) && (isExternalOrCommonJsModule(enclosingDeclaration) || isJsonSourceFile(enclosingDeclaration))) ||
|
|
(isAmbientModule(enclosingDeclaration) && !isGlobalScopeAugmentation(enclosingDeclaration)));
|
|
}
|
|
|
|
// Prepends a `declare` and/or `export` modifier if the context requires it, and then adds `node` to `result` and returns `node`
|
|
// Note: This _mutates_ `node` without using `updateNode` - the assumption being that all nodes should be manufactured fresh by the node builder
|
|
function addResult(node: Statement, additionalModifierFlags: ModifierFlags) {
|
|
let newModifierFlags: ModifierFlags = ModifierFlags.None;
|
|
if (additionalModifierFlags & ModifierFlags.Export &&
|
|
enclosingDeclaration &&
|
|
isExportingScope(enclosingDeclaration) &&
|
|
canHaveExportModifier(node)
|
|
) {
|
|
// Classes, namespaces, variables, functions, interfaces, and types should all be `export`ed in a module context if not private
|
|
newModifierFlags |= ModifierFlags.Export;
|
|
}
|
|
if (addingDeclare && !(newModifierFlags & ModifierFlags.Export) &&
|
|
(!enclosingDeclaration || !(enclosingDeclaration.flags & NodeFlags.Ambient)) &&
|
|
(isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isModuleDeclaration(node))) {
|
|
// Classes, namespaces, variables, enums, and functions all need `declare` modifiers to be valid in a declaration file top-level scope
|
|
newModifierFlags |= ModifierFlags.Ambient;
|
|
}
|
|
if ((additionalModifierFlags & ModifierFlags.Default) && (isClassDeclaration(node) || isInterfaceDeclaration(node) || isFunctionDeclaration(node))) {
|
|
newModifierFlags |= ModifierFlags.Default;
|
|
}
|
|
if (newModifierFlags) {
|
|
node.modifiers = createNodeArray(createModifiersFromModifierFlags(newModifierFlags | getModifierFlags(node)));
|
|
node.modifierFlagsCache = 0; // Reset computed flags cache
|
|
}
|
|
results.push(node);
|
|
}
|
|
|
|
function serializeTypeAlias(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) {
|
|
const aliasType = getDeclaredTypeOfTypeAlias(symbol);
|
|
const typeParams = getSymbolLinks(symbol).typeParameters;
|
|
const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context));
|
|
const jsdocAliasDecl = find(symbol.declarations, isJSDocTypeAlias);
|
|
const commentText = jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined;
|
|
const oldFlags = context.flags;
|
|
context.flags |= NodeBuilderFlags.InTypeAlias;
|
|
addResult(setSyntheticLeadingComments(
|
|
createTypeAliasDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, typeToTypeNodeHelper(aliasType, context)),
|
|
!commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]
|
|
), modifierFlags);
|
|
context.flags = oldFlags;
|
|
}
|
|
|
|
function serializeInterface(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) {
|
|
const interfaceType = getDeclaredTypeOfClassOrInterface(symbol);
|
|
const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
|
|
const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context));
|
|
const baseTypes = getBaseTypes(interfaceType);
|
|
const baseType = length(baseTypes) ? getIntersectionType(baseTypes) : undefined;
|
|
const members = flatMap<Symbol, TypeElement>(getPropertiesOfType(interfaceType), p => serializePropertySymbolForInterface(p, baseType));
|
|
const callSignatures = serializeSignatures(SignatureKind.Call, interfaceType, baseType, SyntaxKind.CallSignature) as CallSignatureDeclaration[];
|
|
const constructSignatures = serializeSignatures(SignatureKind.Construct, interfaceType, baseType, SyntaxKind.ConstructSignature) as ConstructSignatureDeclaration[];
|
|
const indexSignatures = serializeIndexSignatures(interfaceType, baseType);
|
|
|
|
const heritageClauses = !length(baseTypes) ? undefined : [createHeritageClause(SyntaxKind.ExtendsKeyword, mapDefined(baseTypes, b => trySerializeAsTypeReference(b)))];
|
|
addResult(createInterfaceDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
getInternalSymbolName(symbol, symbolName),
|
|
typeParamDecls,
|
|
heritageClauses,
|
|
[...indexSignatures, ...constructSignatures, ...callSignatures, ...members]
|
|
), modifierFlags);
|
|
}
|
|
|
|
function getNamespaceMembersForSerialization(symbol: Symbol) {
|
|
return !symbol.exports ? [] : filter(arrayFrom((symbol.exports).values()), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype")));
|
|
}
|
|
|
|
function isTypeOnlyNamespace(symbol: Symbol) {
|
|
return every(getNamespaceMembersForSerialization(symbol), m => !(resolveSymbol(m).flags & SymbolFlags.Value));
|
|
}
|
|
|
|
function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) {
|
|
const members = getNamespaceMembersForSerialization(symbol);
|
|
// Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging)
|
|
const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol ? "real" : "merged");
|
|
const realMembers = locationMap.get("real") || emptyArray;
|
|
const mergedMembers = locationMap.get("merged") || emptyArray;
|
|
// TODO: `suppressNewPrivateContext` is questionable -we need to simply be emitting privates in whatever scope they were declared in, rather
|
|
// than whatever scope we traverse to them in. That's a bit of a complex rewrite, since we're not _actually_ tracking privates at all in advance,
|
|
// so we don't even have placeholders to fill in.
|
|
if (length(realMembers)) {
|
|
const localName = getInternalSymbolName(symbol, symbolName);
|
|
serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment)));
|
|
}
|
|
if (length(mergedMembers)) {
|
|
const localName = getInternalSymbolName(symbol, symbolName);
|
|
const nsBody = createModuleBlock([createExportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createNamedExports(map(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => {
|
|
const name = unescapeLeadingUnderscores(s.escapedName);
|
|
const localName = getInternalSymbolName(s, name);
|
|
const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s);
|
|
const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true);
|
|
includePrivateSymbol(target || s);
|
|
const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName;
|
|
return createExportSpecifier(name === targetName ? undefined : targetName, name);
|
|
}))
|
|
)]);
|
|
addResult(createModuleDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createIdentifier(localName),
|
|
nsBody,
|
|
NodeFlags.Namespace
|
|
), ModifierFlags.None);
|
|
}
|
|
}
|
|
|
|
function serializeEnum(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) {
|
|
addResult(createEnumDeclaration(
|
|
/*decorators*/ undefined,
|
|
createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0),
|
|
getInternalSymbolName(symbol, symbolName),
|
|
map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => {
|
|
// TODO: Handle computed names
|
|
// I hate that to get the initialized value we need to walk back to the declarations here; but there's no
|
|
// other way to get the possible const value of an enum member that I'm aware of, as the value is cached
|
|
// _on the declaration_, not on the declaration's symbol...
|
|
const initializedValue = p.declarations && p.declarations[0] && isEnumMember(p.declarations[0]) && getConstantValue(p.declarations[0] as EnumMember);
|
|
return createEnumMember(unescapeLeadingUnderscores(p.escapedName), initializedValue === undefined ? undefined : createLiteral(initializedValue));
|
|
})
|
|
), modifierFlags);
|
|
}
|
|
|
|
function serializeVariableOrProperty(symbol: Symbol, symbolName: string, isPrivate: boolean, needsPostExportDefault: boolean, propertyAsAlias: boolean | undefined, modifierFlags: ModifierFlags) {
|
|
if (propertyAsAlias) {
|
|
serializeMaybeAliasAssignment(symbol);
|
|
}
|
|
else {
|
|
const type = getTypeOfSymbol(symbol);
|
|
const localName = getInternalSymbolName(symbol, symbolName);
|
|
if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) {
|
|
// If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns
|
|
serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags);
|
|
}
|
|
else {
|
|
// A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_
|
|
// `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property`
|
|
const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) ? undefined
|
|
: isConstVariable(symbol) ? NodeFlags.Const
|
|
: NodeFlags.Let;
|
|
const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol);
|
|
let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d));
|
|
if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) {
|
|
textRange = textRange.parent.parent;
|
|
}
|
|
const statement = setTextRange(createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([
|
|
createVariableDeclaration(name, serializeTypeForDeclaration(type, symbol))
|
|
], flags)), textRange);
|
|
addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags);
|
|
if (name !== localName && !isPrivate) {
|
|
// We rename the variable declaration we generate for Property symbols since they may have a name which
|
|
// conflicts with a local declaration. For example, given input:
|
|
// ```
|
|
// function g() {}
|
|
// module.exports.g = g
|
|
// ```
|
|
// In such a situation, we have a local variable named `g`, and a separate exported variable named `g`.
|
|
// Naively, we would emit
|
|
// ```
|
|
// function g() {}
|
|
// export const g: typeof g;
|
|
// ```
|
|
// That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but
|
|
// the export declaration shadows it.
|
|
// To work around that, we instead write
|
|
// ```
|
|
// function g() {}
|
|
// const g_1: typeof g;
|
|
// export { g_1 as g };
|
|
// ```
|
|
// To create an export named `g` that does _not_ shadow the local `g`
|
|
addResult(
|
|
createExportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createNamedExports([createExportSpecifier(name, localName)])
|
|
),
|
|
ModifierFlags.None
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function serializeAsFunctionNamespaceMerge(type: Type, symbol: Symbol, localName: string, modifierFlags: ModifierFlags) {
|
|
const signatures = getSignaturesOfType(type, SignatureKind.Call);
|
|
for (const sig of signatures) {
|
|
// Each overload becomes a separate function declaration, in order
|
|
const decl = signatureToSignatureDeclarationHelper(sig, SyntaxKind.FunctionDeclaration, context) as FunctionDeclaration;
|
|
decl.name = createIdentifier(localName);
|
|
addResult(setTextRange(decl, sig.declaration), modifierFlags);
|
|
}
|
|
// Module symbol emit will take care of module-y members, provided it has exports
|
|
if (!(symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && !!symbol.exports && !!symbol.exports.size)) {
|
|
const props = filter(getPropertiesOfType(type), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype")));
|
|
serializeAsNamespaceDeclaration(props, localName, modifierFlags, /*suppressNewPrivateContext*/ true);
|
|
}
|
|
}
|
|
|
|
function serializeAsNamespaceDeclaration(props: readonly Symbol[], localName: string, modifierFlags: ModifierFlags, suppressNewPrivateContext: boolean) {
|
|
if (length(props)) {
|
|
const localVsRemoteMap = arrayToMultiMap(props, p =>
|
|
!length(p.declarations) || some(p.declarations, d =>
|
|
getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!)
|
|
) ? "local" : "remote"
|
|
);
|
|
const localProps = localVsRemoteMap.get("local") || emptyArray;
|
|
// handle remote props first - we need to make an `import` declaration that points at the module containing each remote
|
|
// prop in the outermost scope (TODO: a namespace within a namespace would need to be appropriately handled by this)
|
|
// Example:
|
|
// import Foo_1 = require("./exporter");
|
|
// export namespace ns {
|
|
// import Foo = Foo_1.Foo;
|
|
// export { Foo };
|
|
// export const c: number;
|
|
// }
|
|
// This is needed because in JS, statements like `const x = require("./f")` support both type and value lookup, even if they're
|
|
// normally just value lookup (so it functions kinda like an alias even when it's not an alias)
|
|
// _Usually_, we'll simply print the top-level as an alias instead of a `var` in such situations, however is is theoretically
|
|
// possible to encounter a situation where a type has members from both the current file and other files - in those situations,
|
|
// emit akin to the above would be needed.
|
|
|
|
// Add a namespace
|
|
const fakespace = createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier(localName), createModuleBlock([]), NodeFlags.Namespace);
|
|
fakespace.flags ^= NodeFlags.Synthesized; // unset synthesized so it is usable as an enclosing declaration
|
|
fakespace.parent = enclosingDeclaration as SourceFile | NamespaceDeclaration;
|
|
fakespace.locals = createSymbolTable(props);
|
|
fakespace.symbol = props[0].parent!;
|
|
const oldResults = results;
|
|
results = [];
|
|
const oldAddingDeclare = addingDeclare;
|
|
addingDeclare = false;
|
|
const subcontext = { ...context, enclosingDeclaration: fakespace };
|
|
const oldContext = context;
|
|
context = subcontext;
|
|
// TODO: implement handling for the localVsRemoteMap.get("remote") - should be difficult to trigger (see comment above), as only interesting cross-file js merges should make this possible
|
|
visitSymbolTable(createSymbolTable(localProps), suppressNewPrivateContext, /*propertyAsAlias*/ true);
|
|
context = oldContext;
|
|
addingDeclare = oldAddingDeclare;
|
|
const declarations = results;
|
|
results = oldResults;
|
|
fakespace.flags ^= NodeFlags.Synthesized; // reset synthesized
|
|
fakespace.parent = undefined!;
|
|
fakespace.locals = undefined!;
|
|
fakespace.symbol = undefined!;
|
|
fakespace.body = createModuleBlock(declarations);
|
|
addResult(fakespace, modifierFlags); // namespaces can never be default exported
|
|
}
|
|
}
|
|
|
|
function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) {
|
|
const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
|
|
const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context));
|
|
const classType = getDeclaredTypeOfClassOrInterface(symbol);
|
|
const baseTypes = getBaseTypes(classType);
|
|
const implementsTypes = getImplementsTypes(classType);
|
|
const staticType = getTypeOfSymbol(symbol);
|
|
const staticBaseType = getBaseConstructorTypeOfClass(staticType as InterfaceType);
|
|
const heritageClauses = [
|
|
...!length(baseTypes) ? [] : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))],
|
|
...!length(implementsTypes) ? [] : [createHeritageClause(SyntaxKind.ImplementsKeyword, map(implementsTypes, b => serializeBaseType(b, staticBaseType, localName)))]
|
|
];
|
|
const symbolProps = getPropertiesOfType(classType);
|
|
const publicSymbolProps = filter(symbolProps, s => {
|
|
const valueDecl = s.valueDeclaration;
|
|
Debug.assertIsDefined(valueDecl);
|
|
return !(isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name));
|
|
});
|
|
const hasPrivateIdentifier = some(symbolProps, s => {
|
|
const valueDecl = s.valueDeclaration;
|
|
Debug.assertIsDefined(valueDecl);
|
|
return isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name);
|
|
});
|
|
// Boil down all private properties into a single one.
|
|
const privateProperties = hasPrivateIdentifier ?
|
|
[createProperty(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createPrivateIdentifier("#private"),
|
|
/*questionOrExclamationToken*/ undefined,
|
|
/*type*/ undefined,
|
|
/*initializer*/ undefined,
|
|
)] :
|
|
emptyArray;
|
|
const publicProperties = flatMap<Symbol, ClassElement>(publicSymbolProps, p => serializePropertySymbolForClass(p, /*isStatic*/ false, baseTypes[0]));
|
|
// Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics
|
|
const staticMembers = symbol.flags & (SymbolFlags.Function | SymbolFlags.ValueModule)
|
|
? []
|
|
: flatMap(filter(
|
|
getPropertiesOfType(staticType),
|
|
p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype"
|
|
), p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType));
|
|
const constructors = serializeSignatures(SignatureKind.Construct, staticType, baseTypes[0], SyntaxKind.Constructor) as ConstructorDeclaration[];
|
|
for (const c of constructors) {
|
|
// A constructor's return type and type parameters are supposed to be controlled by the enclosing class declaration
|
|
// `signatureToSignatureDeclarationHelper` appends them regardless, so for now we delete them here
|
|
c.type = undefined;
|
|
c.typeParameters = undefined;
|
|
}
|
|
const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]);
|
|
addResult(setTextRange(createClassDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
localName,
|
|
typeParamDecls,
|
|
heritageClauses,
|
|
[...indexSignatures, ...staticMembers, ...constructors, ...publicProperties, ...privateProperties]
|
|
), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags);
|
|
}
|
|
|
|
function serializeAsAlias(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) {
|
|
// synthesize an alias, eg `export { symbolName as Name }`
|
|
// need to mark the alias `symbol` points at
|
|
// as something we need to serialize as a private declaration as well
|
|
const node = getDeclarationOfAliasSymbol(symbol);
|
|
if (!node) return Debug.fail();
|
|
const target = getMergedSymbol(getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true));
|
|
if (!target) {
|
|
return;
|
|
}
|
|
let verbatimTargetName = unescapeLeadingUnderscores(target.escapedName);
|
|
if (verbatimTargetName === InternalSymbolName.ExportEquals && (compilerOptions.esModuleInterop || compilerOptions.allowSyntheticDefaultImports)) {
|
|
// target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match
|
|
verbatimTargetName = InternalSymbolName.Default;
|
|
}
|
|
const targetName = getInternalSymbolName(target, verbatimTargetName);
|
|
includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first
|
|
switch (node.kind) {
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
// Could be a local `import localName = ns.member` or
|
|
// an external `import localName = require("whatever")`
|
|
const isLocalImport = !(target.flags & SymbolFlags.ValueModule);
|
|
addResult(createImportEqualsDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createIdentifier(localName),
|
|
isLocalImport
|
|
? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false)
|
|
: createExternalModuleReference(createLiteral(getSpecifierForModuleSymbol(symbol, context)))
|
|
), isLocalImport ? modifierFlags : ModifierFlags.None);
|
|
break;
|
|
case SyntaxKind.NamespaceExportDeclaration:
|
|
// export as namespace foo
|
|
// TODO: Not part of a file's local or export symbol tables
|
|
// Is bound into file.symbol.globalExports instead, which we don't currently traverse
|
|
addResult(createNamespaceExportDeclaration(idText((node as NamespaceExportDeclaration).name)), ModifierFlags.None);
|
|
break;
|
|
case SyntaxKind.ImportClause:
|
|
addResult(createImportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createImportClause(createIdentifier(localName), /*namedBindings*/ undefined),
|
|
// We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned
|
|
// And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag
|
|
// In such cases, the `target` refers to the module itself already
|
|
createLiteral(getSpecifierForModuleSymbol(target.parent || target, context))
|
|
), ModifierFlags.None);
|
|
break;
|
|
case SyntaxKind.NamespaceImport:
|
|
addResult(createImportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createImportClause(/*importClause*/ undefined, createNamespaceImport(createIdentifier(localName))),
|
|
createLiteral(getSpecifierForModuleSymbol(target, context))
|
|
), ModifierFlags.None);
|
|
break;
|
|
case SyntaxKind.NamespaceExport:
|
|
addResult(createExportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createNamespaceExport(createIdentifier(localName)),
|
|
createLiteral(getSpecifierForModuleSymbol(target, context))
|
|
), ModifierFlags.None);
|
|
break;
|
|
case SyntaxKind.ImportSpecifier:
|
|
addResult(createImportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createImportClause(/*importClause*/ undefined, createNamedImports([
|
|
createImportSpecifier(
|
|
localName !== verbatimTargetName ? createIdentifier(verbatimTargetName) : undefined,
|
|
createIdentifier(localName)
|
|
)
|
|
])),
|
|
createLiteral(getSpecifierForModuleSymbol(target.parent || target, context))
|
|
), ModifierFlags.None);
|
|
break;
|
|
case SyntaxKind.ExportSpecifier:
|
|
// does not use localName because the symbol name in this case refers to the name in the exports table,
|
|
// which we must exactly preserve
|
|
const specifier = (node.parent.parent as ExportDeclaration).moduleSpecifier;
|
|
// targetName is only used when the target is local, as otherwise the target is an alias that points at
|
|
// another file
|
|
serializeExportSpecifier(
|
|
unescapeLeadingUnderscores(symbol.escapedName),
|
|
specifier ? verbatimTargetName : targetName,
|
|
specifier && isStringLiteralLike(specifier) ? createLiteral(specifier.text) : undefined
|
|
);
|
|
break;
|
|
case SyntaxKind.ExportAssignment:
|
|
serializeMaybeAliasAssignment(symbol);
|
|
break;
|
|
case SyntaxKind.BinaryExpression:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
// Could be best encoded as though an export specifier or as though an export assignment
|
|
// If name is default or export=, do an export assignment
|
|
// Otherwise do an export specifier
|
|
if (symbol.escapedName === InternalSymbolName.Default || symbol.escapedName === InternalSymbolName.ExportEquals) {
|
|
serializeMaybeAliasAssignment(symbol);
|
|
}
|
|
else {
|
|
serializeExportSpecifier(localName, targetName);
|
|
}
|
|
break;
|
|
default:
|
|
return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!");
|
|
}
|
|
}
|
|
|
|
function serializeExportSpecifier(localName: string, targetName: string, specifier?: Expression) {
|
|
addResult(createExportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createNamedExports([createExportSpecifier(localName !== targetName ? targetName : undefined, localName)]),
|
|
specifier
|
|
), ModifierFlags.None);
|
|
}
|
|
|
|
function serializeMaybeAliasAssignment(symbol: Symbol) {
|
|
if (symbol.flags & SymbolFlags.Prototype) {
|
|
return;
|
|
}
|
|
const name = unescapeLeadingUnderscores(symbol.escapedName);
|
|
const isExportEquals = name === InternalSymbolName.ExportEquals;
|
|
const isDefault = name === InternalSymbolName.Default;
|
|
const isExportAssignment = isExportEquals || isDefault;
|
|
// synthesize export = ref
|
|
// ref should refer to either be a locally scoped symbol which we need to emit, or
|
|
// a reference to another namespace/module which we may need to emit an `import` statement for
|
|
const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol);
|
|
// serialize what the alias points to, preserve the declaration's initializer
|
|
const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true);
|
|
// If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const
|
|
if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) {
|
|
// In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it
|
|
// eg, `namespace A { export class B {} }; exports = A.B;`
|
|
// Technically, this is all that's required in the case where the assignment is an entity name expression
|
|
const expr = isExportAssignment ? getExportAssignmentExpression(aliasDecl as ExportAssignment | BinaryExpression) : getPropertyAssignmentAliasLikeExpression(aliasDecl as ShorthandPropertyAssignment | PropertyAssignment | PropertyAccessExpression);
|
|
const first = isEntityNameExpression(expr) ? getFirstNonModuleExportsIdentifier(expr) : undefined;
|
|
const referenced = first && resolveEntityName(first, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, enclosingDeclaration);
|
|
if (referenced || target) {
|
|
includePrivateSymbol(referenced || target);
|
|
}
|
|
|
|
// We disable the context's symbol traker for the duration of this name serialization
|
|
// as, by virtue of being here, the name is required to print something, and we don't want to
|
|
// issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue
|
|
// a visibility error here (as they're not visible within any scope), but we want to hoist them
|
|
// into the containing scope anyway, so we want to skip the visibility checks.
|
|
const oldTrack = context.tracker.trackSymbol;
|
|
context.tracker.trackSymbol = noop;
|
|
if (isExportAssignment) {
|
|
results.push(createExportAssignment(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
isExportEquals,
|
|
symbolToExpression(target, context, SymbolFlags.All)
|
|
));
|
|
}
|
|
else {
|
|
if (first === expr) {
|
|
// serialize as `export {target as name}`
|
|
serializeExportSpecifier(name, idText(first));
|
|
}
|
|
else if (isClassExpression(expr)) {
|
|
serializeExportSpecifier(name, getInternalSymbolName(target, symbolName(target)));
|
|
}
|
|
else {
|
|
// serialize as `import _Ref = t.arg.et; export { _Ref as name }`
|
|
const varName = getUnusedName(name, symbol);
|
|
addResult(createImportEqualsDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createIdentifier(varName),
|
|
symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false)
|
|
), ModifierFlags.None);
|
|
serializeExportSpecifier(name, varName);
|
|
}
|
|
}
|
|
context.tracker.trackSymbol = oldTrack;
|
|
}
|
|
else {
|
|
// serialize as an anonymous property declaration
|
|
const varName = getUnusedName(name, symbol);
|
|
// We have to use `getWidenedType` here since the object within a json file is unwidened within the file
|
|
// (Unwidened types can only exist in expression contexts and should never be serialized)
|
|
const typeToSerialize = getWidenedType(getTypeOfSymbol(getMergedSymbol(symbol)));
|
|
if (isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize, symbol)) {
|
|
// If there are no index signatures and `typeToSerialize` is an object type, emit as a namespace instead of a const
|
|
serializeAsFunctionNamespaceMerge(typeToSerialize, symbol, varName, isExportAssignment ? ModifierFlags.None : ModifierFlags.Export);
|
|
}
|
|
else {
|
|
const statement = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([
|
|
createVariableDeclaration(varName, serializeTypeForDeclaration(typeToSerialize, symbol))
|
|
], NodeFlags.Const));
|
|
addResult(statement, name === varName ? ModifierFlags.Export : ModifierFlags.None);
|
|
}
|
|
if (isExportAssignment) {
|
|
results.push(createExportAssignment(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
isExportEquals,
|
|
createIdentifier(varName)
|
|
));
|
|
}
|
|
else if (name !== varName) {
|
|
serializeExportSpecifier(name, varName);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize: Type, hostSymbol: Symbol) {
|
|
// Only object types which are not constructable, or indexable, whose members all come from the
|
|
// context source file, and whose property names are all valid identifiers and not late-bound, _and_
|
|
// whose input is not type annotated (if the input symbol has an annotation we can reuse, we should prefer it)
|
|
const ctxSrc = getSourceFileOfNode(context.enclosingDeclaration);
|
|
return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) &&
|
|
!getIndexInfoOfType(typeToSerialize, IndexKind.String) &&
|
|
!getIndexInfoOfType(typeToSerialize, IndexKind.Number) &&
|
|
!!(length(getPropertiesOfType(typeToSerialize)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) &&
|
|
!length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK
|
|
!getDeclarationWithTypeAnnotation(hostSymbol) &&
|
|
!(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) &&
|
|
!some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) &&
|
|
!some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) &&
|
|
every(getPropertiesOfType(typeToSerialize), p => isIdentifierText(symbolName(p), languageVersion) && !isStringAKeyword(symbolName(p)));
|
|
}
|
|
|
|
function makeSerializePropertySymbol<T extends Node>(createProperty: (
|
|
decorators: readonly Decorator[] | undefined,
|
|
modifiers: readonly Modifier[] | undefined,
|
|
name: string | PropertyName,
|
|
questionOrExclamationToken: QuestionToken | undefined,
|
|
type: TypeNode | undefined,
|
|
initializer: Expression | undefined
|
|
) => T, methodKind: SyntaxKind, useAccessors: true): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]);
|
|
function makeSerializePropertySymbol<T extends Node>(createProperty: (
|
|
decorators: readonly Decorator[] | undefined,
|
|
modifiers: readonly Modifier[] | undefined,
|
|
name: string | PropertyName,
|
|
questionOrExclamationToken: QuestionToken | undefined,
|
|
type: TypeNode | undefined,
|
|
initializer: Expression | undefined
|
|
) => T, methodKind: SyntaxKind, useAccessors: false): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]);
|
|
function makeSerializePropertySymbol<T extends Node>(createProperty: (
|
|
decorators: readonly Decorator[] | undefined,
|
|
modifiers: readonly Modifier[] | undefined,
|
|
name: string | PropertyName,
|
|
questionOrExclamationToken: QuestionToken | undefined,
|
|
type: TypeNode | undefined,
|
|
initializer: Expression | undefined
|
|
) => T, methodKind: SyntaxKind, useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) {
|
|
return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined) {
|
|
const modifierFlags = getDeclarationModifierFlagsFromSymbol(p);
|
|
const isPrivate = !!(modifierFlags & ModifierFlags.Private);
|
|
if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) {
|
|
// Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols
|
|
// need to be merged namespace members
|
|
return [];
|
|
}
|
|
if (p.flags & SymbolFlags.Prototype || (baseType && getPropertyOfType(baseType, p.escapedName)
|
|
&& isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p)
|
|
&& (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional)
|
|
&& isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) {
|
|
return [];
|
|
}
|
|
const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0);
|
|
const name = getPropertyNameNodeForSymbol(p, context);
|
|
const firstPropertyLikeDecl = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression));
|
|
if (p.flags & SymbolFlags.Accessor && useAccessors) {
|
|
const result: AccessorDeclaration[] = [];
|
|
if (p.flags & SymbolFlags.SetAccessor) {
|
|
result.push(setTextRange(createSetAccessor(
|
|
/*decorators*/ undefined,
|
|
createModifiersFromModifierFlags(flag),
|
|
name,
|
|
[createParameter(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
/*dotDotDotToken*/ undefined,
|
|
"arg",
|
|
/*questionToken*/ undefined,
|
|
isPrivate ? undefined : serializeTypeForDeclaration(getTypeOfSymbol(p), p)
|
|
)],
|
|
/*body*/ undefined
|
|
), find(p.declarations, isSetAccessor) || firstPropertyLikeDecl));
|
|
}
|
|
if (p.flags & SymbolFlags.GetAccessor) {
|
|
const isPrivate = modifierFlags & ModifierFlags.Private;
|
|
result.push(setTextRange(createGetAccessor(
|
|
/*decorators*/ undefined,
|
|
createModifiersFromModifierFlags(flag),
|
|
name,
|
|
[],
|
|
isPrivate ? undefined : serializeTypeForDeclaration(getTypeOfSymbol(p), p),
|
|
/*body*/ undefined
|
|
), find(p.declarations, isGetAccessor) || firstPropertyLikeDecl));
|
|
}
|
|
return result;
|
|
}
|
|
// This is an else/if as accessors and properties can't merge in TS, but might in JS
|
|
// If this happens, we assume the accessor takes priority, as it imposes more constraints
|
|
else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable)) {
|
|
return setTextRange(createProperty(
|
|
/*decorators*/ undefined,
|
|
createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag),
|
|
name,
|
|
p.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined,
|
|
isPrivate ? undefined : serializeTypeForDeclaration(getTypeOfSymbol(p), p),
|
|
// TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357
|
|
// interface members can't have initializers, however class members _can_
|
|
/*initializer*/ undefined
|
|
), find(p.declarations, or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl);
|
|
}
|
|
if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) {
|
|
const type = getTypeOfSymbol(p);
|
|
const signatures = getSignaturesOfType(type, SignatureKind.Call);
|
|
if (flag & ModifierFlags.Private) {
|
|
return setTextRange(createProperty(
|
|
/*decorators*/ undefined,
|
|
createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag),
|
|
name,
|
|
p.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined,
|
|
/*type*/ undefined,
|
|
/*initializer*/ undefined
|
|
), find(p.declarations, isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations[0]);
|
|
}
|
|
|
|
const results = [];
|
|
for (const sig of signatures) {
|
|
// Each overload becomes a separate method declaration, in order
|
|
const decl = signatureToSignatureDeclarationHelper(sig, methodKind, context) as MethodDeclaration;
|
|
decl.name = name; // TODO: Clone
|
|
if (flag) {
|
|
decl.modifiers = createNodeArray(createModifiersFromModifierFlags(flag));
|
|
}
|
|
if (p.flags & SymbolFlags.Optional) {
|
|
decl.questionToken = createToken(SyntaxKind.QuestionToken);
|
|
}
|
|
results.push(setTextRange(decl, sig.declaration));
|
|
}
|
|
return results as unknown as T[];
|
|
}
|
|
// The `Constructor`'s symbol isn't in the class's properties lists, obviously, since it's a signature on the static
|
|
return Debug.fail(`Unhandled class member kind! ${(p as any).__debugFlags || p.flags}`);
|
|
};
|
|
}
|
|
|
|
function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) {
|
|
return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType);
|
|
}
|
|
|
|
function getDeclarationWithTypeAnnotation(symbol: Symbol) {
|
|
return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && !!findAncestor(s, n => n === enclosingDeclaration));
|
|
}
|
|
|
|
/**
|
|
* Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag
|
|
* so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym`
|
|
*/
|
|
function serializeTypeForDeclaration(type: Type, symbol: Symbol) {
|
|
const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol);
|
|
if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation)) {
|
|
// try to reuse the existing annotation
|
|
const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!;
|
|
const transformed = visitNode(existing, visitExistingNodeTreeSymbols);
|
|
return transformed === existing ? getMutableClone(existing) : transformed;
|
|
}
|
|
const oldFlags = context.flags;
|
|
if (type.flags & TypeFlags.UniqueESSymbol &&
|
|
type.symbol === symbol) {
|
|
context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType;
|
|
}
|
|
const result = typeToTypeNodeHelper(type, context);
|
|
context.flags = oldFlags;
|
|
return result;
|
|
|
|
function visitExistingNodeTreeSymbols<T extends Node>(node: T): Node {
|
|
// We don't _actually_ support jsdoc namepath types, emit `any` instead
|
|
if (isJSDocAllType(node) || node.kind === SyntaxKind.JSDocNamepathType) {
|
|
return createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
|
}
|
|
if (isJSDocUnknownType(node)) {
|
|
return createKeywordTypeNode(SyntaxKind.UnknownKeyword);
|
|
}
|
|
if (isJSDocNullableType(node)) {
|
|
return createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), createKeywordTypeNode(SyntaxKind.NullKeyword)]);
|
|
}
|
|
if (isJSDocOptionalType(node)) {
|
|
return createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
|
|
}
|
|
if (isJSDocNonNullableType(node)) {
|
|
return visitNode(node.type, visitExistingNodeTreeSymbols);
|
|
}
|
|
if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) {
|
|
return createTypeLiteralNode([createIndexSignature(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
[createParameter(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
/*dotdotdotToken*/ undefined,
|
|
"x",
|
|
/*questionToken*/ undefined,
|
|
visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols)
|
|
)],
|
|
visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols)
|
|
)]);
|
|
}
|
|
if (isJSDocFunctionType(node)) {
|
|
if (isJSDocConstructSignature(node)) {
|
|
let newTypeNode: TypeNode | undefined;
|
|
return createConstructorTypeNode(
|
|
visitNodes(node.typeParameters, visitExistingNodeTreeSymbols),
|
|
mapDefined(node.parameters, (p, i) => p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : createParameter(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
p.dotDotDotToken,
|
|
p.name || p.dotDotDotToken ? `args` : `arg${i}`,
|
|
p.questionToken,
|
|
visitNode(p.type, visitExistingNodeTreeSymbols),
|
|
/*initializer*/ undefined
|
|
)),
|
|
visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols)
|
|
);
|
|
}
|
|
else {
|
|
return createFunctionTypeNode(
|
|
visitNodes(node.typeParameters, visitExistingNodeTreeSymbols),
|
|
map(node.parameters, (p, i) => createParameter(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
p.dotDotDotToken,
|
|
p.name || p.dotDotDotToken ? `args` : `arg${i}`,
|
|
p.questionToken,
|
|
visitNode(p.type, visitExistingNodeTreeSymbols),
|
|
/*initializer*/ undefined
|
|
)),
|
|
visitNode(node.type, visitExistingNodeTreeSymbols)
|
|
);
|
|
}
|
|
}
|
|
if (isLiteralImportTypeNode(node)) {
|
|
return updateImportTypeNode(
|
|
node,
|
|
updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)),
|
|
node.qualifier,
|
|
visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
|
|
node.isTypeOf
|
|
);
|
|
}
|
|
|
|
if (isEntityName(node) || isEntityNameExpression(node)) {
|
|
const leftmost = getFirstIdentifier(node);
|
|
const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true);
|
|
if (sym) {
|
|
includePrivateSymbol(sym);
|
|
if (isIdentifier(node) && sym.flags & SymbolFlags.TypeParameter) {
|
|
const name = typeParameterToName(getDeclaredTypeOfSymbol(sym), context);
|
|
if (idText(name) !== idText(node)) {
|
|
return name;
|
|
}
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
|
|
return visitEachChild(node, visitExistingNodeTreeSymbols, nullTransformationContext);
|
|
}
|
|
|
|
function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) {
|
|
if (bundled) {
|
|
if (context.tracker && context.tracker.moduleResolverHost) {
|
|
const targetFile = getExternalModuleFileFromDeclaration(parent);
|
|
if (targetFile) {
|
|
const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames);
|
|
const resolverHost = {
|
|
getCanonicalFileName,
|
|
getCurrentDirectory: context.tracker.moduleResolverHost.getCurrentDirectory ? () => context.tracker.moduleResolverHost!.getCurrentDirectory!() : () => "",
|
|
getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory()
|
|
};
|
|
const newName = getResolvedExternalModuleName(resolverHost, targetFile);
|
|
return createLiteral(newName);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (context.tracker && context.tracker.trackExternalModuleSymbolOfImportTypeNode) {
|
|
const moduleSym = resolveExternalModuleNameWorker(lit, lit, /*moduleNotFoundError*/ undefined);
|
|
if (moduleSym) {
|
|
context.tracker.trackExternalModuleSymbolOfImportTypeNode(moduleSym);
|
|
}
|
|
}
|
|
}
|
|
return lit;
|
|
}
|
|
}
|
|
|
|
function serializeSignatures(kind: SignatureKind, input: Type, baseType: Type | undefined, outputKind: SyntaxKind) {
|
|
const signatures = getSignaturesOfType(input, kind);
|
|
if (kind === SignatureKind.Construct) {
|
|
if (!baseType && every(signatures, s => length(s.parameters) === 0)) {
|
|
return []; // No base type, every constructor is empty - elide the extraneous `constructor()`
|
|
}
|
|
if (baseType) {
|
|
// If there is a base type, if every signature in the class is identical to a signature in the baseType, elide all the declarations
|
|
const baseSigs = getSignaturesOfType(baseType, SignatureKind.Construct);
|
|
if (!length(baseSigs) && every(signatures, s => length(s.parameters) === 0)) {
|
|
return []; // Base had no explicit signatures, if all our signatures are also implicit, return an empty list
|
|
}
|
|
if (baseSigs.length === signatures.length) {
|
|
let failed = false;
|
|
for (let i = 0; i < baseSigs.length; i++) {
|
|
if (!compareSignaturesIdentical(signatures[i], baseSigs[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) {
|
|
failed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!failed) {
|
|
return []; // Every signature was identical - elide constructor list as it is inherited
|
|
}
|
|
}
|
|
}
|
|
let privateProtected: ModifierFlags = 0;
|
|
for (const s of signatures) {
|
|
if (s.declaration) {
|
|
privateProtected |= getSelectedModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected);
|
|
}
|
|
}
|
|
if (privateProtected) {
|
|
return [setTextRange(createConstructor(
|
|
/*decorators*/ undefined,
|
|
createModifiersFromModifierFlags(privateProtected),
|
|
/*parameters*/ [],
|
|
/*body*/ undefined,
|
|
), signatures[0].declaration)];
|
|
}
|
|
}
|
|
|
|
const results = [];
|
|
for (const sig of signatures) {
|
|
// Each overload becomes a separate constructor declaration, in order
|
|
const decl = signatureToSignatureDeclarationHelper(sig, outputKind, context);
|
|
results.push(setTextRange(decl, sig.declaration));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function serializeIndexSignatures(input: Type, baseType: Type | undefined) {
|
|
const results: IndexSignatureDeclaration[] = [];
|
|
for (const type of [IndexKind.String, IndexKind.Number]) {
|
|
const info = getIndexInfoOfType(input, type);
|
|
if (info) {
|
|
if (baseType) {
|
|
const baseInfo = getIndexInfoOfType(baseType, type);
|
|
if (baseInfo) {
|
|
if (isTypeIdenticalTo(info.type, baseInfo.type)) {
|
|
continue; // elide identical index signatures
|
|
}
|
|
}
|
|
}
|
|
results.push(indexInfoToIndexSignatureDeclarationHelper(info, type, context));
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function serializeBaseType(t: Type, staticType: Type, rootName: string) {
|
|
const ref = trySerializeAsTypeReference(t);
|
|
if (ref) {
|
|
return ref;
|
|
}
|
|
const tempName = getUnusedName(`${rootName}_base`);
|
|
const statement = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([
|
|
createVariableDeclaration(tempName, typeToTypeNodeHelper(staticType, context))
|
|
], NodeFlags.Const));
|
|
addResult(statement, ModifierFlags.None);
|
|
return createExpressionWithTypeArguments(/*typeArgs*/ undefined, createIdentifier(tempName));
|
|
}
|
|
|
|
function trySerializeAsTypeReference(t: Type) {
|
|
let typeArgs: TypeNode[] | undefined;
|
|
let reference: Expression | undefined;
|
|
// We don't use `isValueSymbolAccessible` below. since that considers alternative containers (like modules)
|
|
// which we can't write out in a syntactically valid way as an expression
|
|
if ((t as TypeReference).target && getAccessibleSymbolChain((t as TypeReference).target.symbol, enclosingDeclaration, SymbolFlags.Value, /*useOnlyExternalAliasing*/ false)) {
|
|
typeArgs = map(getTypeArguments(t as TypeReference), t => typeToTypeNodeHelper(t, context));
|
|
reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type);
|
|
}
|
|
else if (t.symbol && getAccessibleSymbolChain(t.symbol, enclosingDeclaration, SymbolFlags.Value, /*useOnlyExternalAliasing*/ false)) {
|
|
reference = symbolToExpression(t.symbol, context, SymbolFlags.Type);
|
|
}
|
|
if (reference) {
|
|
return createExpressionWithTypeArguments(typeArgs, reference);
|
|
}
|
|
}
|
|
|
|
function getUnusedName(input: string, symbol?: Symbol): string {
|
|
if (symbol) {
|
|
if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) {
|
|
return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!;
|
|
}
|
|
}
|
|
if (symbol) {
|
|
input = getNameCandidateWorker(symbol, input);
|
|
}
|
|
let i = 0;
|
|
const original = input;
|
|
while (context.usedSymbolNames!.has(input)) {
|
|
i++;
|
|
input = `${original}_${i}`;
|
|
}
|
|
context.usedSymbolNames!.set(input, true);
|
|
if (symbol) {
|
|
context.remappedSymbolNames!.set("" + getSymbolId(symbol), input);
|
|
}
|
|
return input;
|
|
}
|
|
|
|
function getNameCandidateWorker(symbol: Symbol, localName: string) {
|
|
if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) {
|
|
const flags = context.flags;
|
|
context.flags |= NodeBuilderFlags.InInitialEntityName;
|
|
const nameCandidate = getNameOfSymbolAsWritten(symbol, context);
|
|
context.flags = flags;
|
|
localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate;
|
|
}
|
|
if (localName === InternalSymbolName.Default) {
|
|
localName = "_default";
|
|
}
|
|
else if (localName === InternalSymbolName.ExportEquals) {
|
|
localName = "_exports";
|
|
}
|
|
localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-zA-Z0-9]/g, "_");
|
|
return localName;
|
|
}
|
|
|
|
function getInternalSymbolName(symbol: Symbol, localName: string) {
|
|
if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) {
|
|
return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!;
|
|
}
|
|
localName = getNameCandidateWorker(symbol, localName);
|
|
// The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up
|
|
context.remappedSymbolNames!.set("" + getSymbolId(symbol), localName);
|
|
return localName;
|
|
}
|
|
}
|
|
}
|
|
|
|
function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string {
|
|
return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker);
|
|
|
|
function typePredicateToStringWorker(writer: EmitTextWriter) {
|
|
const predicate = createTypePredicateNodeWithModifier(
|
|
typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? createToken(SyntaxKind.AssertsKeyword) : undefined,
|
|
typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? createIdentifier(typePredicate.parameterName) : createThisTypeNode(),
|
|
typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)! // TODO: GH#18217
|
|
);
|
|
const printer = createPrinter({ removeComments: true });
|
|
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
|
|
printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer);
|
|
return writer;
|
|
}
|
|
}
|
|
|
|
function formatUnionTypes(types: readonly Type[]): Type[] {
|
|
const result: Type[] = [];
|
|
let flags: TypeFlags = 0;
|
|
for (let i = 0; i < types.length; i++) {
|
|
const t = types[i];
|
|
flags |= t.flags;
|
|
if (!(t.flags & TypeFlags.Nullable)) {
|
|
if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) {
|
|
const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(<LiteralType>t);
|
|
if (baseType.flags & TypeFlags.Union) {
|
|
const count = (<UnionType>baseType).types.length;
|
|
if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((<UnionType>baseType).types[count - 1])) {
|
|
result.push(baseType);
|
|
i += count - 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
result.push(t);
|
|
}
|
|
}
|
|
if (flags & TypeFlags.Null) result.push(nullType);
|
|
if (flags & TypeFlags.Undefined) result.push(undefinedType);
|
|
return result || types;
|
|
}
|
|
|
|
function visibilityToString(flags: ModifierFlags): string | undefined {
|
|
if (flags === ModifierFlags.Private) {
|
|
return "private";
|
|
}
|
|
if (flags === ModifierFlags.Protected) {
|
|
return "protected";
|
|
}
|
|
return "public";
|
|
}
|
|
|
|
function getTypeAliasForTypeLiteral(type: Type): Symbol | undefined {
|
|
if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) {
|
|
const node = findAncestor(type.symbol.declarations[0].parent, n => n.kind !== SyntaxKind.ParenthesizedType)!;
|
|
if (node.kind === SyntaxKind.TypeAliasDeclaration) {
|
|
return getSymbolOfNode(node);
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function isTopLevelInExternalModuleAugmentation(node: Node): boolean {
|
|
return node && node.parent &&
|
|
node.parent.kind === SyntaxKind.ModuleBlock &&
|
|
isExternalModuleAugmentation(node.parent.parent);
|
|
}
|
|
|
|
interface NodeBuilderContext {
|
|
enclosingDeclaration: Node | undefined;
|
|
flags: NodeBuilderFlags;
|
|
tracker: SymbolTracker;
|
|
|
|
// State
|
|
encounteredError: boolean;
|
|
visitedTypes: Map<true> | undefined;
|
|
symbolDepth: Map<number> | undefined;
|
|
inferTypeParameters: TypeParameter[] | undefined;
|
|
approximateLength: number;
|
|
truncating?: boolean;
|
|
typeParameterSymbolList?: Map<true>;
|
|
typeParameterNames?: Map<Identifier>;
|
|
typeParameterNamesByText?: Map<true>;
|
|
usedSymbolNames?: Map<true>;
|
|
remappedSymbolNames?: Map<string>;
|
|
}
|
|
|
|
function isDefaultBindingContext(location: Node) {
|
|
return location.kind === SyntaxKind.SourceFile || isAmbientModule(location);
|
|
}
|
|
|
|
function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) {
|
|
const nameType = getSymbolLinks(symbol).nameType;
|
|
if (nameType) {
|
|
if (nameType.flags & TypeFlags.StringOrNumberLiteral) {
|
|
const name = "" + (<StringLiteralType | NumberLiteralType>nameType).value;
|
|
if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) {
|
|
return `"${escapeString(name, CharacterCodes.doubleQuote)}"`;
|
|
}
|
|
if (isNumericLiteralName(name) && startsWith(name, "-")) {
|
|
return `[${name}]`;
|
|
}
|
|
return name;
|
|
}
|
|
if (nameType.flags & TypeFlags.UniqueESSymbol) {
|
|
return `[${getNameOfSymbolAsWritten((<UniqueESSymbolType>nameType).symbol, context)}]`;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a human-readable name for a symbol.
|
|
* Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead.
|
|
*
|
|
* Unlike `symbolName(symbol)`, this will include quotes if the name is from a string literal.
|
|
* It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`.
|
|
*/
|
|
function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string {
|
|
if (context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) &&
|
|
// If it's not the first part of an entity name, it must print as `default`
|
|
(!(context.flags & NodeBuilderFlags.InInitialEntityName) ||
|
|
// if the symbol is synthesized, it will only be referenced externally it must print as `default`
|
|
!symbol.declarations ||
|
|
// if not in the same binding context (source file, module declaration), it must print as `default`
|
|
(context.enclosingDeclaration && findAncestor(symbol.declarations[0], isDefaultBindingContext) !== findAncestor(context.enclosingDeclaration, isDefaultBindingContext)))) {
|
|
return "default";
|
|
}
|
|
if (symbol.declarations && symbol.declarations.length) {
|
|
let declaration = firstDefined(symbol.declarations, d => getNameOfDeclaration(d) ? d : undefined); // Try using a declaration with a name, first
|
|
const name = declaration && getNameOfDeclaration(declaration);
|
|
if (declaration && name) {
|
|
if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) {
|
|
return symbolName(symbol);
|
|
}
|
|
if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late)) {
|
|
const nameType = getSymbolLinks(symbol).nameType;
|
|
if (nameType && nameType.flags & TypeFlags.StringOrNumberLiteral) {
|
|
// Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name
|
|
const result = getNameOfSymbolFromNameType(symbol, context);
|
|
if (result !== undefined) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
return declarationNameToString(name);
|
|
}
|
|
if (!declaration) {
|
|
declaration = symbol.declarations[0]; // Declaration may be nameless, but we'll try anyway
|
|
}
|
|
if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) {
|
|
return declarationNameToString((<VariableDeclaration>declaration.parent).name);
|
|
}
|
|
switch (declaration.kind) {
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) {
|
|
context.encounteredError = true;
|
|
}
|
|
return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)";
|
|
}
|
|
}
|
|
const name = getNameOfSymbolFromNameType(symbol, context);
|
|
return name !== undefined ? name : symbolName(symbol);
|
|
}
|
|
|
|
function isDeclarationVisible(node: Node): boolean {
|
|
if (node) {
|
|
const links = getNodeLinks(node);
|
|
if (links.isVisible === undefined) {
|
|
links.isVisible = !!determineIfDeclarationIsVisible();
|
|
}
|
|
return links.isVisible;
|
|
}
|
|
|
|
return false;
|
|
|
|
function determineIfDeclarationIsVisible() {
|
|
switch (node.kind) {
|
|
case SyntaxKind.JSDocCallbackTag:
|
|
case SyntaxKind.JSDocTypedefTag:
|
|
case SyntaxKind.JSDocEnumTag:
|
|
// Top-level jsdoc type aliases are considered exported
|
|
// First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file
|
|
return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent));
|
|
case SyntaxKind.BindingElement:
|
|
return isDeclarationVisible(node.parent.parent);
|
|
case SyntaxKind.VariableDeclaration:
|
|
if (isBindingPattern((node as VariableDeclaration).name) &&
|
|
!((node as VariableDeclaration).name as BindingPattern).elements.length) {
|
|
// If the binding pattern is empty, this variable declaration is not visible
|
|
return false;
|
|
}
|
|
// falls through
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
// external module augmentation is always visible
|
|
if (isExternalModuleAugmentation(node)) {
|
|
return true;
|
|
}
|
|
const parent = getDeclarationContainer(node);
|
|
// If the node is not exported or it is not ambient module element (except import declaration)
|
|
if (!(getCombinedModifierFlags(node as Declaration) & ModifierFlags.Export) &&
|
|
!(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && parent.flags & NodeFlags.Ambient)) {
|
|
return isGlobalSourceFile(parent);
|
|
}
|
|
// Exported members/ambient module elements (exception import declaration) are visible if parent is visible
|
|
return isDeclarationVisible(parent);
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
if (hasModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) {
|
|
// Private/protected properties/methods are not visible
|
|
return false;
|
|
}
|
|
// Public properties/methods are visible if its parents are visible, so:
|
|
// falls through
|
|
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.IndexSignature:
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.ModuleBlock:
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
case SyntaxKind.TypeLiteral:
|
|
case SyntaxKind.TypeReference:
|
|
case SyntaxKind.ArrayType:
|
|
case SyntaxKind.TupleType:
|
|
case SyntaxKind.UnionType:
|
|
case SyntaxKind.IntersectionType:
|
|
case SyntaxKind.ParenthesizedType:
|
|
return isDeclarationVisible(node.parent);
|
|
|
|
// Default binding, import specifier and namespace import is visible
|
|
// only on demand so by default it is not visible
|
|
case SyntaxKind.ImportClause:
|
|
case SyntaxKind.NamespaceImport:
|
|
case SyntaxKind.ImportSpecifier:
|
|
return false;
|
|
|
|
// Type parameters are always visible
|
|
case SyntaxKind.TypeParameter:
|
|
|
|
// Source file and namespace export are always visible
|
|
// falls through
|
|
case SyntaxKind.SourceFile:
|
|
case SyntaxKind.NamespaceExportDeclaration:
|
|
return true;
|
|
|
|
// Export assignments do not create name bindings outside the module
|
|
case SyntaxKind.ExportAssignment:
|
|
return false;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined {
|
|
let exportSymbol: Symbol | undefined;
|
|
if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) {
|
|
exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false);
|
|
}
|
|
else if (node.parent.kind === SyntaxKind.ExportSpecifier) {
|
|
exportSymbol = getTargetOfExportSpecifier(<ExportSpecifier>node.parent, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
|
|
}
|
|
let result: Node[] | undefined;
|
|
let visited: Map<true> | undefined;
|
|
if (exportSymbol) {
|
|
visited = createMap();
|
|
visited.set("" + getSymbolId(exportSymbol), true);
|
|
buildVisibleNodeList(exportSymbol.declarations);
|
|
}
|
|
return result;
|
|
|
|
function buildVisibleNodeList(declarations: Declaration[]) {
|
|
forEach(declarations, declaration => {
|
|
const resultNode = getAnyImportSyntax(declaration) || declaration;
|
|
if (setVisibility) {
|
|
getNodeLinks(declaration).isVisible = true;
|
|
}
|
|
else {
|
|
result = result || [];
|
|
pushIfUnique(result, resultNode);
|
|
}
|
|
|
|
if (isInternalModuleImportEqualsDeclaration(declaration)) {
|
|
// Add the referenced top container visible
|
|
const internalModuleReference = <Identifier | QualifiedName>declaration.moduleReference;
|
|
const firstIdentifier = getFirstIdentifier(internalModuleReference);
|
|
const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace,
|
|
undefined, undefined, /*isUse*/ false);
|
|
const id = importSymbol && "" + getSymbolId(importSymbol);
|
|
if (importSymbol && !visited!.has(id!)) {
|
|
visited!.set(id!, true);
|
|
buildVisibleNodeList(importSymbol.declarations);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push an entry on the type resolution stack. If an entry with the given target and the given property name
|
|
* is already on the stack, and no entries in between already have a type, then a circularity has occurred.
|
|
* In this case, the result values of the existing entry and all entries pushed after it are changed to false,
|
|
* and the value false is returned. Otherwise, the new entry is just pushed onto the stack, and true is returned.
|
|
* In order to see if the same query has already been done before, the target object and the propertyName both
|
|
* must match the one passed in.
|
|
*
|
|
* @param target The symbol, type, or signature whose type is being queried
|
|
* @param propertyName The property name that should be used to query the target for its type
|
|
*/
|
|
function pushTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean {
|
|
const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName);
|
|
if (resolutionCycleStartIndex >= 0) {
|
|
// A cycle was found
|
|
const { length } = resolutionTargets;
|
|
for (let i = resolutionCycleStartIndex; i < length; i++) {
|
|
resolutionResults[i] = false;
|
|
}
|
|
return false;
|
|
}
|
|
resolutionTargets.push(target);
|
|
resolutionResults.push(/*items*/ true);
|
|
resolutionPropertyNames.push(propertyName);
|
|
return true;
|
|
}
|
|
|
|
function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number {
|
|
for (let i = resolutionTargets.length - 1; i >= 0; i--) {
|
|
if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) {
|
|
return -1;
|
|
}
|
|
if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean {
|
|
switch (propertyName) {
|
|
case TypeSystemPropertyName.Type:
|
|
return !!getSymbolLinks(<Symbol>target).type;
|
|
case TypeSystemPropertyName.EnumTagType:
|
|
return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType);
|
|
case TypeSystemPropertyName.DeclaredType:
|
|
return !!getSymbolLinks(<Symbol>target).declaredType;
|
|
case TypeSystemPropertyName.ResolvedBaseConstructorType:
|
|
return !!(<InterfaceType>target).resolvedBaseConstructorType;
|
|
case TypeSystemPropertyName.ResolvedReturnType:
|
|
return !!(<Signature>target).resolvedReturnType;
|
|
case TypeSystemPropertyName.ImmediateBaseConstraint:
|
|
return !!(<Type>target).immediateBaseConstraint;
|
|
case TypeSystemPropertyName.ResolvedTypeArguments:
|
|
return !!(target as TypeReference).resolvedTypeArguments;
|
|
}
|
|
return Debug.assertNever(propertyName);
|
|
}
|
|
|
|
/**
|
|
* Pop an entry from the type resolution stack and return its associated result value. The result value will
|
|
* be true if no circularities were detected, or false if a circularity was found.
|
|
*/
|
|
function popTypeResolution(): boolean {
|
|
resolutionTargets.pop();
|
|
resolutionPropertyNames.pop();
|
|
return resolutionResults.pop()!;
|
|
}
|
|
|
|
function getDeclarationContainer(node: Node): Node {
|
|
return findAncestor(getRootDeclaration(node), node => {
|
|
switch (node.kind) {
|
|
case SyntaxKind.VariableDeclaration:
|
|
case SyntaxKind.VariableDeclarationList:
|
|
case SyntaxKind.ImportSpecifier:
|
|
case SyntaxKind.NamedImports:
|
|
case SyntaxKind.NamespaceImport:
|
|
case SyntaxKind.ImportClause:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
})!.parent;
|
|
}
|
|
|
|
function getTypeOfPrototypeProperty(prototype: Symbol): Type {
|
|
// TypeScript 1.0 spec (April 2014): 8.4
|
|
// Every class automatically contains a static property member named 'prototype',
|
|
// the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter.
|
|
// It is an error to explicitly declare a static property member with the name 'prototype'.
|
|
const classType = <InterfaceType>getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!);
|
|
return classType.typeParameters ? createTypeReference(<GenericType>classType, map(classType.typeParameters, _ => anyType)) : classType;
|
|
}
|
|
|
|
// Return the type of the given property in the given type, or undefined if no such property exists
|
|
function getTypeOfPropertyOfType(type: Type, name: __String): Type | undefined {
|
|
const prop = getPropertyOfType(type, name);
|
|
return prop ? getTypeOfSymbol(prop) : undefined;
|
|
}
|
|
|
|
function getTypeOfPropertyOrIndexSignature(type: Type, name: __String): Type {
|
|
return getTypeOfPropertyOfType(type, name) || isNumericLiteralName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || unknownType;
|
|
}
|
|
|
|
function isTypeAny(type: Type | undefined) {
|
|
return type && (type.flags & TypeFlags.Any) !== 0;
|
|
}
|
|
|
|
// Return the type of a binding element parent. We check SymbolLinks first to see if a type has been
|
|
// assigned by contextual typing.
|
|
function getTypeForBindingElementParent(node: BindingElementGrandparent) {
|
|
const symbol = getSymbolOfNode(node);
|
|
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false);
|
|
}
|
|
|
|
function isComputedNonLiteralName(name: PropertyName): boolean {
|
|
return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteralLike(name.expression);
|
|
}
|
|
|
|
function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type {
|
|
source = filterType(source, t => !(t.flags & TypeFlags.Nullable));
|
|
if (source.flags & TypeFlags.Never) {
|
|
return emptyObjectType;
|
|
}
|
|
if (source.flags & TypeFlags.Union) {
|
|
return mapType(source, t => getRestType(t, properties, symbol));
|
|
}
|
|
const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName));
|
|
if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) {
|
|
if (omitKeyType.flags & TypeFlags.Never) {
|
|
return source;
|
|
}
|
|
|
|
const omitTypeAlias = getGlobalOmitSymbol();
|
|
if (!omitTypeAlias) {
|
|
return errorType;
|
|
}
|
|
return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]);
|
|
}
|
|
const members = createSymbolTable();
|
|
for (const prop of getPropertiesOfType(source)) {
|
|
if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType)
|
|
&& !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected))
|
|
&& isSpreadableProperty(prop)) {
|
|
members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false));
|
|
}
|
|
}
|
|
const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String);
|
|
const numberIndexInfo = getIndexInfoOfType(source, IndexKind.Number);
|
|
const result = createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
|
|
result.objectFlags |= ObjectFlags.ObjectRestType;
|
|
return result;
|
|
}
|
|
|
|
// Determine the control flow type associated with a destructuring declaration or assignment. The following
|
|
// forms of destructuring are possible:
|
|
// let { x } = obj; // BindingElement
|
|
// let [ x ] = obj; // BindingElement
|
|
// { x } = obj; // ShorthandPropertyAssignment
|
|
// { x: v } = obj; // PropertyAssignment
|
|
// [ x ] = obj; // Expression
|
|
// We construct a synthetic element access expression corresponding to 'obj.x' such that the control
|
|
// flow analyzer doesn't have to handle all the different syntactic forms.
|
|
function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) {
|
|
const reference = getSyntheticElementAccess(node);
|
|
return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType;
|
|
}
|
|
|
|
function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined {
|
|
const parentAccess = getParentElementAccess(node);
|
|
if (parentAccess && parentAccess.flowNode) {
|
|
const propName = getDestructuringPropertyName(node);
|
|
if (propName) {
|
|
const result = <ElementAccessExpression>createNode(SyntaxKind.ElementAccessExpression, node.pos, node.end);
|
|
result.parent = node;
|
|
result.expression = <LeftHandSideExpression>parentAccess;
|
|
const literal = <StringLiteral>createNode(SyntaxKind.StringLiteral, node.pos, node.end);
|
|
literal.parent = result;
|
|
literal.text = propName;
|
|
result.argumentExpression = literal;
|
|
result.flowNode = parentAccess.flowNode;
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) {
|
|
const ancestor = node.parent.parent;
|
|
switch (ancestor.kind) {
|
|
case SyntaxKind.BindingElement:
|
|
case SyntaxKind.PropertyAssignment:
|
|
return getSyntheticElementAccess(<BindingElement | PropertyAssignment>ancestor);
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return getSyntheticElementAccess(<Expression>node.parent);
|
|
case SyntaxKind.VariableDeclaration:
|
|
return (<VariableDeclaration>ancestor).initializer;
|
|
case SyntaxKind.BinaryExpression:
|
|
return (<BinaryExpression>ancestor).right;
|
|
}
|
|
}
|
|
|
|
function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) {
|
|
const parent = node.parent;
|
|
if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) {
|
|
return getLiteralPropertyNameText((<BindingElement>node).propertyName || <Identifier>(<BindingElement>node).name);
|
|
}
|
|
if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
return getLiteralPropertyNameText((<PropertyAssignment | ShorthandPropertyAssignment>node).name);
|
|
}
|
|
return "" + (<NodeArray<Node>>(<BindingPattern | ArrayLiteralExpression>parent).elements).indexOf(node);
|
|
}
|
|
|
|
function getLiteralPropertyNameText(name: PropertyName) {
|
|
const type = getLiteralTypeFromPropertyName(name);
|
|
return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (<StringLiteralType | NumberLiteralType>type).value : undefined;
|
|
}
|
|
|
|
/** Return the inferred type for a binding element */
|
|
function getTypeForBindingElement(declaration: BindingElement): Type | undefined {
|
|
const pattern = declaration.parent;
|
|
let parentType = getTypeForBindingElementParent(pattern.parent);
|
|
// If no type or an any type was inferred for parent, infer that for the binding element
|
|
if (!parentType || isTypeAny(parentType)) {
|
|
return parentType;
|
|
}
|
|
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
|
|
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
|
|
parentType = getNonNullableType(parentType);
|
|
}
|
|
|
|
let type: Type | undefined;
|
|
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
|
|
if (declaration.dotDotDotToken) {
|
|
parentType = getReducedType(parentType);
|
|
if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) {
|
|
error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types);
|
|
return errorType;
|
|
}
|
|
const literalMembers: PropertyName[] = [];
|
|
for (const element of pattern.elements) {
|
|
if (!element.dotDotDotToken) {
|
|
literalMembers.push(element.propertyName || element.name as Identifier);
|
|
}
|
|
}
|
|
type = getRestType(parentType, literalMembers, declaration.symbol);
|
|
}
|
|
else {
|
|
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
|
|
const name = declaration.propertyName || <Identifier>declaration.name;
|
|
const indexType = getLiteralTypeFromPropertyName(name);
|
|
const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, name), declaration.name);
|
|
type = getFlowTypeOfDestructuring(declaration, declaredType);
|
|
}
|
|
}
|
|
else {
|
|
// This elementType will be used if the specific property corresponding to this index is not
|
|
// present (aka the tuple element property). This call also checks that the parentType is in
|
|
// fact an iterable or array (depending on target language).
|
|
const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, parentType, undefinedType, pattern);
|
|
const index = pattern.elements.indexOf(declaration);
|
|
if (declaration.dotDotDotToken) {
|
|
// If the parent is a tuple type, the rest element has a tuple type of the
|
|
// remaining tuple element types. Otherwise, the rest element has an array type with same
|
|
// element type as the parent type.
|
|
type = everyType(parentType, isTupleType) ?
|
|
mapType(parentType, t => sliceTupleType(<TupleTypeReference>t, index)) :
|
|
createArrayType(elementType);
|
|
}
|
|
else if (isArrayLikeType(parentType)) {
|
|
const indexType = getLiteralType(index);
|
|
const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0;
|
|
const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, declaration.name, accessFlags) || errorType, declaration.name);
|
|
type = getFlowTypeOfDestructuring(declaration, declaredType);
|
|
}
|
|
else {
|
|
type = elementType;
|
|
}
|
|
}
|
|
if (!declaration.initializer) {
|
|
return type;
|
|
}
|
|
if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) {
|
|
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
|
|
// undefined from the final type.
|
|
return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ?
|
|
getTypeWithFacts(type, TypeFacts.NEUndefined) :
|
|
type;
|
|
}
|
|
return widenTypeInferredFromInitializer(declaration, getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype));
|
|
}
|
|
|
|
function getTypeForDeclarationFromJSDocComment(declaration: Node) {
|
|
const jsdocType = getJSDocType(declaration);
|
|
if (jsdocType) {
|
|
return getTypeFromTypeNode(jsdocType);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function isNullOrUndefined(node: Expression) {
|
|
const expr = skipParentheses(node);
|
|
return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>expr) === undefinedSymbol;
|
|
}
|
|
|
|
function isEmptyArrayLiteral(node: Expression) {
|
|
const expr = skipParentheses(node);
|
|
return expr.kind === SyntaxKind.ArrayLiteralExpression && (<ArrayLiteralExpression>expr).elements.length === 0;
|
|
}
|
|
|
|
function addOptionality(type: Type, optional = true): Type {
|
|
return strictNullChecks && optional ? getOptionalType(type) : type;
|
|
}
|
|
|
|
// Return the inferred type for a variable, parameter, or property declaration
|
|
function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, includeOptionality: boolean): Type | undefined {
|
|
// A variable declared in a for..in statement is of type string, or of type keyof T when the
|
|
// right hand expression is of a type parameter type.
|
|
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) {
|
|
const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression)));
|
|
return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType;
|
|
}
|
|
|
|
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
|
|
// checkRightHandSideOfForOf will return undefined if the for-of expression type was
|
|
// missing properties/signatures required to get its iteratedType (like
|
|
// [Symbol.iterator] or next). This may be because we accessed properties from anyType,
|
|
// or it may have led to an error inside getElementTypeOfIterable.
|
|
const forOfStatement = declaration.parent.parent;
|
|
return checkRightHandSideOfForOf(forOfStatement.expression, forOfStatement.awaitModifier) || anyType;
|
|
}
|
|
|
|
if (isBindingPattern(declaration.parent)) {
|
|
return getTypeForBindingElement(<BindingElement>declaration);
|
|
}
|
|
|
|
const isOptional = includeOptionality && (
|
|
isParameter(declaration) && isJSDocOptionalParameter(declaration)
|
|
|| !isBindingElement(declaration) && !isVariableDeclaration(declaration) && !!declaration.questionToken);
|
|
|
|
// Use type from type annotation if one is present
|
|
const declaredType = tryGetTypeFromEffectiveTypeNode(declaration);
|
|
if (declaredType) {
|
|
return addOptionality(declaredType, isOptional);
|
|
}
|
|
|
|
if ((noImplicitAny || isInJSFile(declaration)) &&
|
|
declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) &&
|
|
!(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !(declaration.flags & NodeFlags.Ambient)) {
|
|
// If --noImplicitAny is on or the declaration is in a Javascript file,
|
|
// use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no
|
|
// initializer or a 'null' or 'undefined' initializer.
|
|
if (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) {
|
|
return autoType;
|
|
}
|
|
// Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array
|
|
// literal initializer.
|
|
if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) {
|
|
return autoArrayType;
|
|
}
|
|
}
|
|
|
|
if (declaration.kind === SyntaxKind.Parameter) {
|
|
const func = <FunctionLikeDeclaration>declaration.parent;
|
|
// For a parameter of a set accessor, use the type of the get accessor if one is present
|
|
if (func.kind === SyntaxKind.SetAccessor && !hasNonBindableDynamicName(func)) {
|
|
const getter = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(declaration.parent), SyntaxKind.GetAccessor);
|
|
if (getter) {
|
|
const getterSignature = getSignatureFromDeclaration(getter);
|
|
const thisParameter = getAccessorThisParameter(func as AccessorDeclaration);
|
|
if (thisParameter && declaration === thisParameter) {
|
|
// Use the type from the *getter*
|
|
Debug.assert(!thisParameter.type);
|
|
return getTypeOfSymbol(getterSignature.thisParameter!);
|
|
}
|
|
return getReturnTypeOfSignature(getterSignature);
|
|
}
|
|
}
|
|
if (isInJSFile(declaration)) {
|
|
const typeTag = getJSDocType(func);
|
|
if (typeTag && isFunctionTypeNode(typeTag)) {
|
|
return getTypeAtPosition(getSignatureFromDeclaration(typeTag), func.parameters.indexOf(declaration));
|
|
}
|
|
}
|
|
// Use contextual parameter type if one is available
|
|
const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration);
|
|
if (type) {
|
|
return addOptionality(type, isOptional);
|
|
}
|
|
}
|
|
else if (isInJSFile(declaration)) {
|
|
const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfNode(declaration), getDeclaredExpandoInitializer(declaration));
|
|
if (containerObjectType) {
|
|
return containerObjectType;
|
|
}
|
|
}
|
|
|
|
// Use the type of the initializer expression if one is present and the declaration is
|
|
// not a parameter of a contextually typed function
|
|
if (declaration.initializer) {
|
|
const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration));
|
|
return addOptionality(type, isOptional);
|
|
}
|
|
|
|
if (isJsxAttribute(declaration)) {
|
|
// if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true.
|
|
// I.e <Elem attr /> is sugar for <Elem attr={true} />
|
|
return trueType;
|
|
}
|
|
|
|
// If the declaration specifies a binding pattern and is not a parameter of a contextually
|
|
// typed function, use the type implied by the binding pattern
|
|
if (isBindingPattern(declaration.name)) {
|
|
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true);
|
|
}
|
|
|
|
// No type specified and nothing can be inferred
|
|
return undefined;
|
|
}
|
|
|
|
function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) {
|
|
// function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers
|
|
const container = getAssignedExpandoInitializer(symbol.valueDeclaration);
|
|
if (container) {
|
|
const tag = getJSDocTypeTag(container);
|
|
if (tag && tag.typeExpression) {
|
|
return getTypeFromTypeNode(tag.typeExpression);
|
|
}
|
|
const containerObjectType = getJSContainerObjectType(symbol.valueDeclaration, symbol, container);
|
|
return containerObjectType || getWidenedLiteralType(checkExpressionCached(container));
|
|
}
|
|
let definedInConstructor = false;
|
|
let definedInMethod = false;
|
|
let jsdocType: Type | undefined;
|
|
let types: Type[] | undefined;
|
|
for (const declaration of symbol.declarations) {
|
|
const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration :
|
|
isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration :
|
|
undefined;
|
|
if (!expression) {
|
|
continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere
|
|
}
|
|
|
|
const kind = isAccessExpression(expression)
|
|
? getAssignmentDeclarationPropertyAccessKind(expression)
|
|
: getAssignmentDeclarationKind(expression);
|
|
if (kind === AssignmentDeclarationKind.ThisProperty) {
|
|
if (isDeclarationInConstructor(expression)) {
|
|
definedInConstructor = true;
|
|
}
|
|
else {
|
|
definedInMethod = true;
|
|
}
|
|
}
|
|
if (!isCallExpression(expression)) {
|
|
jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration);
|
|
}
|
|
if (!jsdocType) {
|
|
(types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType);
|
|
}
|
|
}
|
|
let type = jsdocType;
|
|
if (!type) {
|
|
if (!length(types)) {
|
|
return errorType; // No types from any declarations :(
|
|
}
|
|
let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined;
|
|
// use only the constructor types unless they were only assigned null | undefined (including widening variants)
|
|
if (definedInMethod) {
|
|
const propType = getTypeOfAssignmentDeclarationPropertyOfBaseType(symbol);
|
|
if (propType) {
|
|
(constructorTypes || (constructorTypes = [])).push(propType);
|
|
definedInConstructor = true;
|
|
}
|
|
}
|
|
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217
|
|
type = getUnionType(sourceTypes!, UnionReduction.Subtype);
|
|
}
|
|
const widened = getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor));
|
|
if (filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) {
|
|
reportImplicitAny(symbol.valueDeclaration, anyType);
|
|
return anyType;
|
|
}
|
|
return widened;
|
|
}
|
|
|
|
function getJSContainerObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined {
|
|
if (!isInJSFile(decl) || !init || !isObjectLiteralExpression(init) || init.properties.length) {
|
|
return undefined;
|
|
}
|
|
const exports = createSymbolTable();
|
|
while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) {
|
|
const s = getSymbolOfNode(decl);
|
|
if (s && hasEntries(s.exports)) {
|
|
mergeSymbolTable(exports, s.exports);
|
|
}
|
|
decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent;
|
|
}
|
|
const s = getSymbolOfNode(decl);
|
|
if (s && hasEntries(s.exports)) {
|
|
mergeSymbolTable(exports, s.exports);
|
|
}
|
|
const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, undefined, undefined);
|
|
type.objectFlags |= ObjectFlags.JSLiteral;
|
|
return type;
|
|
}
|
|
|
|
function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) {
|
|
const typeNode = getEffectiveTypeAnnotationNode(expression.parent);
|
|
if (typeNode) {
|
|
const type = getWidenedType(getTypeFromTypeNode(typeNode));
|
|
if (!declaredType) {
|
|
return type;
|
|
}
|
|
else if (declaredType !== errorType && type !== errorType && !isTypeIdenticalTo(declaredType, type)) {
|
|
errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type);
|
|
}
|
|
}
|
|
if (symbol.parent) {
|
|
const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration);
|
|
if (typeNode) {
|
|
return getTypeOfPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName);
|
|
}
|
|
}
|
|
|
|
return declaredType;
|
|
}
|
|
|
|
/** If we don't have an explicit JSDoc type, get the type from the initializer. */
|
|
function getInitializerTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) {
|
|
if (isCallExpression(expression)) {
|
|
if (resolvedSymbol) {
|
|
return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments
|
|
}
|
|
const objectLitType = checkExpressionCached((expression as BindableObjectDefinePropertyCall).arguments[2]);
|
|
const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String);
|
|
if (valueType) {
|
|
return valueType;
|
|
}
|
|
const getFunc = getTypeOfPropertyOfType(objectLitType, "get" as __String);
|
|
if (getFunc) {
|
|
const getSig = getSingleCallSignature(getFunc);
|
|
if (getSig) {
|
|
return getReturnTypeOfSignature(getSig);
|
|
}
|
|
}
|
|
const setFunc = getTypeOfPropertyOfType(objectLitType, "set" as __String);
|
|
if (setFunc) {
|
|
const setSig = getSingleCallSignature(setFunc);
|
|
if (setSig) {
|
|
return getTypeOfFirstParameterOfSignature(setSig);
|
|
}
|
|
}
|
|
return anyType;
|
|
}
|
|
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
|
|
if (type.flags & TypeFlags.Object &&
|
|
kind === AssignmentDeclarationKind.ModuleExports &&
|
|
symbol.escapedName === InternalSymbolName.ExportEquals) {
|
|
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
|
|
const members = createSymbolTable();
|
|
copyEntries(exportedType.members, members);
|
|
if (resolvedSymbol && !resolvedSymbol.exports) {
|
|
resolvedSymbol.exports = createSymbolTable();
|
|
}
|
|
(resolvedSymbol || symbol).exports!.forEach((s, name) => {
|
|
if (members.has(name)) {
|
|
const exportedMember = exportedType.members.get(name)!;
|
|
const union = createSymbol(s.flags | exportedMember.flags, name);
|
|
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]);
|
|
members.set(name, union);
|
|
}
|
|
else {
|
|
members.set(name, s);
|
|
}
|
|
});
|
|
const result = createAnonymousType(
|
|
exportedType.symbol,
|
|
members,
|
|
exportedType.callSignatures,
|
|
exportedType.constructSignatures,
|
|
exportedType.stringIndexInfo,
|
|
exportedType.numberIndexInfo);
|
|
result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag
|
|
return result;
|
|
}
|
|
if (isEmptyArrayLiteralType(type)) {
|
|
reportImplicitAny(expression, anyArrayType);
|
|
return anyArrayType;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function isDeclarationInConstructor(expression: Expression) {
|
|
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
|
|
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
|
|
// Function expressions that are assigned to the prototype count as methods.
|
|
return thisContainer.kind === SyntaxKind.Constructor ||
|
|
thisContainer.kind === SyntaxKind.FunctionDeclaration ||
|
|
(thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent));
|
|
}
|
|
|
|
function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined {
|
|
Debug.assert(types.length === declarations.length);
|
|
return types.filter((_, i) => {
|
|
const declaration = declarations[i];
|
|
const expression = isBinaryExpression(declaration) ? declaration :
|
|
isBinaryExpression(declaration.parent) ? declaration.parent : undefined;
|
|
return expression && isDeclarationInConstructor(expression);
|
|
});
|
|
}
|
|
|
|
/** check for definition in base class if any declaration is in a class */
|
|
function getTypeOfAssignmentDeclarationPropertyOfBaseType(property: Symbol) {
|
|
const parentDeclaration = forEach(property.declarations, d => {
|
|
const parent = getThisContainer(d, /*includeArrowFunctions*/ false).parent;
|
|
return isClassLike(parent) && parent;
|
|
});
|
|
if (parentDeclaration) {
|
|
const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(parentDeclaration)) as InterfaceType;
|
|
const baseClassType = classType && getBaseTypes(classType)[0];
|
|
if (baseClassType) {
|
|
return getTypeOfPropertyOfType(baseClassType, property.escapedName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the type implied by a binding pattern element. This is the type of the initializer of the element if
|
|
// one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding
|
|
// pattern. Otherwise, it is the type any.
|
|
function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type {
|
|
if (element.initializer) {
|
|
// The type implied by a binding pattern is independent of context, so we check the initializer with no
|
|
// contextual type or, if the element itself is a binding pattern, with the type implied by that binding
|
|
// pattern.
|
|
const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType;
|
|
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, contextualType)));
|
|
}
|
|
if (isBindingPattern(element.name)) {
|
|
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
|
|
}
|
|
if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) {
|
|
reportImplicitAny(element, anyType);
|
|
}
|
|
// When we're including the pattern in the type (an indication we're obtaining a contextual type), we
|
|
// use the non-inferrable any type. Inference will never directly infer this type, but it is possible
|
|
// to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases,
|
|
// widening of the binding pattern type substitutes a regular any for the non-inferrable any.
|
|
return includePatternInType ? nonInferrableAnyType : anyType;
|
|
}
|
|
|
|
// Return the type implied by an object binding pattern
|
|
function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type {
|
|
const members = createSymbolTable();
|
|
let stringIndexInfo: IndexInfo | undefined;
|
|
let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
|
|
forEach(pattern.elements, e => {
|
|
const name = e.propertyName || <Identifier>e.name;
|
|
if (e.dotDotDotToken) {
|
|
stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
|
|
return;
|
|
}
|
|
|
|
const exprType = getLiteralTypeFromPropertyName(name);
|
|
if (!isTypeUsableAsPropertyName(exprType)) {
|
|
// do not include computed properties in the implied type
|
|
objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties;
|
|
return;
|
|
}
|
|
const text = getPropertyNameFromType(exprType);
|
|
const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0);
|
|
const symbol = createSymbol(flags, text);
|
|
symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors);
|
|
symbol.bindingElement = e;
|
|
members.set(symbol.escapedName, symbol);
|
|
});
|
|
const result = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, undefined);
|
|
result.objectFlags |= objectFlags;
|
|
if (includePatternInType) {
|
|
result.pattern = pattern;
|
|
result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Return the type implied by an array binding pattern
|
|
function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type {
|
|
const elements = pattern.elements;
|
|
const lastElement = lastOrUndefined(elements);
|
|
const hasRestElement = !!(lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken);
|
|
if (elements.length === 0 || elements.length === 1 && hasRestElement) {
|
|
return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType;
|
|
}
|
|
const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors));
|
|
const minLength = findLastIndex(elements, e => !isOmittedExpression(e) && !hasDefaultValue(e), elements.length - (hasRestElement ? 2 : 1)) + 1;
|
|
let result = <TypeReference>createTupleType(elementTypes, minLength, hasRestElement);
|
|
if (includePatternInType) {
|
|
result = cloneTypeReference(result);
|
|
result.pattern = pattern;
|
|
result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself
|
|
// and without regard to its context (i.e. without regard any type annotation or initializer associated with the
|
|
// declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any]
|
|
// and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is
|
|
// used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring
|
|
// parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of
|
|
// the parameter.
|
|
function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type {
|
|
return pattern.kind === SyntaxKind.ObjectBindingPattern
|
|
? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors)
|
|
: getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors);
|
|
}
|
|
|
|
// Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type
|
|
// specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it
|
|
// is a bit more involved. For example:
|
|
//
|
|
// var [x, s = ""] = [1, "one"];
|
|
//
|
|
// Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the
|
|
// binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the
|
|
// tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string.
|
|
function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, reportErrors?: boolean): Type {
|
|
return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true), declaration, reportErrors);
|
|
}
|
|
|
|
function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) {
|
|
if (type) {
|
|
if (reportErrors) {
|
|
reportErrorsFromWidening(declaration, type);
|
|
}
|
|
|
|
// always widen a 'unique symbol' type if the type was created for a different declaration.
|
|
if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !declaration.type) && type.symbol !== getSymbolOfNode(declaration)) {
|
|
type = esSymbolType;
|
|
}
|
|
|
|
return getWidenedType(type);
|
|
}
|
|
|
|
// Rest parameters default to type any[], other parameters default to type any
|
|
type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType;
|
|
|
|
// Report implicit any errors unless this is a private property within an ambient declaration
|
|
if (reportErrors) {
|
|
if (!declarationBelongsToPrivateAmbientMember(declaration)) {
|
|
reportImplicitAny(declaration, type);
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function declarationBelongsToPrivateAmbientMember(declaration: VariableLikeDeclaration) {
|
|
const root = getRootDeclaration(declaration);
|
|
const memberDeclaration = root.kind === SyntaxKind.Parameter ? root.parent : root;
|
|
return isPrivateWithinAmbient(memberDeclaration);
|
|
}
|
|
|
|
function tryGetTypeFromEffectiveTypeNode(declaration: Declaration) {
|
|
const typeNode = getEffectiveTypeAnnotationNode(declaration);
|
|
if (typeNode) {
|
|
return getTypeFromTypeNode(typeNode);
|
|
}
|
|
}
|
|
|
|
function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.type) {
|
|
const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol);
|
|
// For a contextually typed parameter it is possible that a type has already
|
|
// been assigned (in assignTypeToParameterAndFixTypeParameters), and we want
|
|
// to preserve this type.
|
|
if (!links.type) {
|
|
links.type = type;
|
|
}
|
|
}
|
|
return links.type;
|
|
}
|
|
|
|
function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol) {
|
|
// Handle prototype property
|
|
if (symbol.flags & SymbolFlags.Prototype) {
|
|
return getTypeOfPrototypeProperty(symbol);
|
|
}
|
|
// CommonsJS require and module both have type any.
|
|
if (symbol === requireSymbol) {
|
|
return anyType;
|
|
}
|
|
if (symbol.flags & SymbolFlags.ModuleExports) {
|
|
const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration));
|
|
const members = createSymbolTable();
|
|
members.set("exports" as __String, fileSymbol);
|
|
return createAnonymousType(symbol, members, emptyArray, emptyArray, undefined, undefined);
|
|
}
|
|
// Handle catch clause variables
|
|
const declaration = symbol.valueDeclaration;
|
|
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
|
|
return anyType;
|
|
}
|
|
// Handle export default expressions
|
|
if (isSourceFile(declaration) && isJsonSourceFile(declaration)) {
|
|
if (!declaration.statements.length) {
|
|
return emptyObjectType;
|
|
}
|
|
return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression)));
|
|
}
|
|
|
|
// Handle variable, parameter or property
|
|
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
|
|
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
|
|
if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) {
|
|
return getTypeOfFuncClassEnumModule(symbol);
|
|
}
|
|
return reportCircularityError(symbol);
|
|
}
|
|
let type: Type | undefined;
|
|
if (declaration.kind === SyntaxKind.ExportAssignment) {
|
|
type = widenTypeForVariableLikeDeclaration(checkExpressionCached((<ExportAssignment>declaration).expression), declaration);
|
|
}
|
|
else if (
|
|
isBinaryExpression(declaration) ||
|
|
(isInJSFile(declaration) &&
|
|
(isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) {
|
|
type = getWidenedTypeForAssignmentDeclaration(symbol);
|
|
}
|
|
else if (isJSDocPropertyLikeTag(declaration)
|
|
|| isPropertyAccessExpression(declaration)
|
|
|| isElementAccessExpression(declaration)
|
|
|| isIdentifier(declaration)
|
|
|| isStringLiteralLike(declaration)
|
|
|| isNumericLiteral(declaration)
|
|
|| isClassDeclaration(declaration)
|
|
|| isFunctionDeclaration(declaration)
|
|
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
|
|
|| isMethodSignature(declaration)
|
|
|| isSourceFile(declaration)) {
|
|
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
|
|
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
|
|
return getTypeOfFuncClassEnumModule(symbol);
|
|
}
|
|
type = isBinaryExpression(declaration.parent) ?
|
|
getWidenedTypeForAssignmentDeclaration(symbol) :
|
|
tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
|
|
}
|
|
else if (isPropertyAssignment(declaration)) {
|
|
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration);
|
|
}
|
|
else if (isJsxAttribute(declaration)) {
|
|
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration);
|
|
}
|
|
else if (isShorthandPropertyAssignment(declaration)) {
|
|
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal);
|
|
}
|
|
else if (isObjectLiteralMethod(declaration)) {
|
|
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal);
|
|
}
|
|
else if (isParameter(declaration)
|
|
|| isPropertyDeclaration(declaration)
|
|
|| isPropertySignature(declaration)
|
|
|| isVariableDeclaration(declaration)
|
|
|| isBindingElement(declaration)) {
|
|
type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
|
|
}
|
|
// getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive.
|
|
// Re-dispatch based on valueDeclaration.kind instead.
|
|
else if (isEnumDeclaration(declaration)) {
|
|
type = getTypeOfFuncClassEnumModule(symbol);
|
|
}
|
|
else if (isEnumMember(declaration)) {
|
|
type = getTypeOfEnumMember(symbol);
|
|
}
|
|
else if (isAccessor(declaration)) {
|
|
type = resolveTypeOfAccessors(symbol);
|
|
}
|
|
else {
|
|
return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol));
|
|
}
|
|
|
|
if (!popTypeResolution()) {
|
|
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
|
|
if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) {
|
|
return getTypeOfFuncClassEnumModule(symbol);
|
|
}
|
|
return reportCircularityError(symbol);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | undefined): TypeNode | undefined {
|
|
if (accessor) {
|
|
if (accessor.kind === SyntaxKind.GetAccessor) {
|
|
const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor);
|
|
return getterTypeAnnotation;
|
|
}
|
|
else {
|
|
const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor);
|
|
return setterTypeAnnotation;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getAnnotatedAccessorType(accessor: AccessorDeclaration | undefined): Type | undefined {
|
|
const node = getAnnotatedAccessorTypeNode(accessor);
|
|
return node && getTypeFromTypeNode(node);
|
|
}
|
|
|
|
function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): Symbol | undefined {
|
|
const parameter = getAccessorThisParameter(accessor);
|
|
return parameter && parameter.symbol;
|
|
}
|
|
|
|
function getThisTypeOfDeclaration(declaration: SignatureDeclaration): Type | undefined {
|
|
return getThisTypeOfSignature(getSignatureFromDeclaration(declaration));
|
|
}
|
|
|
|
function getTypeOfAccessors(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
return links.type || (links.type = getTypeOfAccessorsWorker(symbol));
|
|
}
|
|
|
|
function getTypeOfAccessorsWorker(symbol: Symbol): Type {
|
|
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
|
|
return errorType;
|
|
}
|
|
|
|
let type = resolveTypeOfAccessors(symbol);
|
|
|
|
if (!popTypeResolution()) {
|
|
type = anyType;
|
|
if (noImplicitAny) {
|
|
const getter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.GetAccessor);
|
|
error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol));
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function resolveTypeOfAccessors(symbol: Symbol) {
|
|
const getter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.GetAccessor);
|
|
const setter = getDeclarationOfKind<AccessorDeclaration>(symbol, SyntaxKind.SetAccessor);
|
|
|
|
if (getter && isInJSFile(getter)) {
|
|
const jsDocType = getTypeForDeclarationFromJSDocComment(getter);
|
|
if (jsDocType) {
|
|
return jsDocType;
|
|
}
|
|
}
|
|
// First try to see if the user specified a return type on the get-accessor.
|
|
const getterReturnType = getAnnotatedAccessorType(getter);
|
|
if (getterReturnType) {
|
|
return getterReturnType;
|
|
}
|
|
else {
|
|
// If the user didn't specify a return type, try to use the set-accessor's parameter type.
|
|
const setterParameterType = getAnnotatedAccessorType(setter);
|
|
if (setterParameterType) {
|
|
return setterParameterType;
|
|
}
|
|
else {
|
|
// If there are no specified types, try to infer it from the body of the get accessor if it exists.
|
|
if (getter && getter.body) {
|
|
return getReturnTypeFromBody(getter);
|
|
}
|
|
// Otherwise, fall back to 'any'.
|
|
else {
|
|
if (setter) {
|
|
if (!isPrivateWithinAmbient(setter)) {
|
|
errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol));
|
|
}
|
|
}
|
|
else {
|
|
Debug.assert(!!getter, "there must exist a getter as we are current checking either setter or getter in this function");
|
|
if (!isPrivateWithinAmbient(getter)) {
|
|
errorOrSuggestion(noImplicitAny, getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol));
|
|
}
|
|
}
|
|
return anyType;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getBaseTypeVariableOfClass(symbol: Symbol) {
|
|
const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol));
|
|
return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType :
|
|
baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) :
|
|
undefined;
|
|
}
|
|
|
|
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
|
|
let links = getSymbolLinks(symbol);
|
|
const originalLinks = links;
|
|
if (!links.type) {
|
|
const jsDeclaration = getDeclarationOfExpando(symbol.valueDeclaration);
|
|
if (jsDeclaration) {
|
|
const merged = mergeJSSymbols(symbol, getSymbolOfNode(jsDeclaration));
|
|
if (merged) {
|
|
// note:we overwrite links because we just cloned the symbol
|
|
symbol = links = merged;
|
|
}
|
|
}
|
|
originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol);
|
|
}
|
|
return links.type;
|
|
}
|
|
|
|
function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type {
|
|
const declaration = symbol.valueDeclaration;
|
|
if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) {
|
|
return anyType;
|
|
}
|
|
else if (declaration.kind === SyntaxKind.BinaryExpression ||
|
|
isAccessExpression(declaration) &&
|
|
declaration.parent.kind === SyntaxKind.BinaryExpression) {
|
|
return getWidenedTypeForAssignmentDeclaration(symbol);
|
|
}
|
|
else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) {
|
|
const resolvedModule = resolveExternalModuleSymbol(symbol);
|
|
if (resolvedModule !== symbol) {
|
|
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
|
|
return errorType;
|
|
}
|
|
const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!);
|
|
const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
|
|
if (!popTypeResolution()) {
|
|
return reportCircularityError(symbol);
|
|
}
|
|
return type;
|
|
}
|
|
}
|
|
const type = createObjectType(ObjectFlags.Anonymous, symbol);
|
|
if (symbol.flags & SymbolFlags.Class) {
|
|
const baseTypeVariable = getBaseTypeVariableOfClass(symbol);
|
|
return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type;
|
|
}
|
|
else {
|
|
return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type;
|
|
}
|
|
}
|
|
|
|
function getTypeOfEnumMember(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol));
|
|
}
|
|
|
|
function getTypeOfAlias(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.type) {
|
|
const targetSymbol = resolveAlias(symbol);
|
|
|
|
// It only makes sense to get the type of a value symbol. If the result of resolving
|
|
// the alias is not a value, then it has no type. To get the type associated with a
|
|
// type symbol, call getDeclaredTypeOfSymbol.
|
|
// This check is important because without it, a call to getTypeOfSymbol could end
|
|
// up recursively calling getTypeOfAlias, causing a stack overflow.
|
|
links.type = targetSymbol.flags & SymbolFlags.Value
|
|
? getTypeOfSymbol(targetSymbol)
|
|
: errorType;
|
|
}
|
|
return links.type;
|
|
}
|
|
|
|
function getTypeOfInstantiatedSymbol(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.type) {
|
|
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
|
|
return links.type = errorType;
|
|
}
|
|
let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper);
|
|
if (!popTypeResolution()) {
|
|
type = reportCircularityError(symbol);
|
|
}
|
|
links.type = type;
|
|
}
|
|
return links.type;
|
|
}
|
|
|
|
function reportCircularityError(symbol: Symbol) {
|
|
const declaration = <VariableLikeDeclaration>symbol.valueDeclaration;
|
|
// Check if variable has type annotation that circularly references the variable itself
|
|
if (getEffectiveTypeAnnotationNode(declaration)) {
|
|
error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation,
|
|
symbolToString(symbol));
|
|
return errorType;
|
|
}
|
|
// Check if variable has initializer that circularly references the variable itself
|
|
if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (<HasInitializer>declaration).initializer)) {
|
|
error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer,
|
|
symbolToString(symbol));
|
|
}
|
|
// Circularities could also result from parameters in function expressions that end up
|
|
// having themselves as contextual types following type argument inference. In those cases
|
|
// we have already reported an implicit any error so we don't report anything here.
|
|
return anyType;
|
|
}
|
|
|
|
function getTypeOfSymbolWithDeferredType(symbol: Symbol) {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.type) {
|
|
Debug.assertIsDefined(links.deferralParent);
|
|
Debug.assertIsDefined(links.deferralConstituents);
|
|
links.type = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents) : getIntersectionType(links.deferralConstituents);
|
|
}
|
|
return links.type;
|
|
}
|
|
|
|
function getTypeOfSymbol(symbol: Symbol): Type {
|
|
const checkFlags = getCheckFlags(symbol);
|
|
if (checkFlags & CheckFlags.DeferredType) {
|
|
return getTypeOfSymbolWithDeferredType(symbol);
|
|
}
|
|
if (checkFlags & CheckFlags.Instantiated) {
|
|
return getTypeOfInstantiatedSymbol(symbol);
|
|
}
|
|
if (checkFlags & CheckFlags.Mapped) {
|
|
return getTypeOfMappedSymbol(symbol as MappedSymbol);
|
|
}
|
|
if (checkFlags & CheckFlags.ReverseMapped) {
|
|
return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol);
|
|
}
|
|
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
|
|
return getTypeOfVariableOrParameterOrProperty(symbol);
|
|
}
|
|
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
|
|
return getTypeOfFuncClassEnumModule(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.EnumMember) {
|
|
return getTypeOfEnumMember(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Accessor) {
|
|
return getTypeOfAccessors(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Alias) {
|
|
return getTypeOfAlias(symbol);
|
|
}
|
|
return errorType;
|
|
}
|
|
|
|
function isReferenceToType(type: Type, target: Type) {
|
|
return type !== undefined
|
|
&& target !== undefined
|
|
&& (getObjectFlags(type) & ObjectFlags.Reference) !== 0
|
|
&& (<TypeReference>type).target === target;
|
|
}
|
|
|
|
function getTargetType(type: Type): Type {
|
|
return getObjectFlags(type) & ObjectFlags.Reference ? (<TypeReference>type).target : type;
|
|
}
|
|
|
|
// TODO: GH#18217 If `checkBase` is undefined, we should not call this because this will always return false.
|
|
function hasBaseType(type: Type, checkBase: Type | undefined) {
|
|
return check(type);
|
|
function check(type: Type): boolean {
|
|
if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) {
|
|
const target = <InterfaceType>getTargetType(type);
|
|
return target === checkBase || some(getBaseTypes(target), check);
|
|
}
|
|
else if (type.flags & TypeFlags.Intersection) {
|
|
return some((<IntersectionType>type).types, check);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set.
|
|
// The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set
|
|
// in-place and returns the same array.
|
|
function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: readonly TypeParameterDeclaration[]): TypeParameter[] | undefined {
|
|
for (const declaration of declarations) {
|
|
typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration)));
|
|
}
|
|
return typeParameters;
|
|
}
|
|
|
|
// Return the outer type parameters of a node or undefined if the node has no outer type parameters.
|
|
function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] | undefined {
|
|
while (true) {
|
|
node = node.parent; // TODO: GH#18217 Use SourceFile kind check instead
|
|
if (node && isBinaryExpression(node)) {
|
|
// prototype assignments get the outer type parameters of their constructor function
|
|
const assignmentKind = getAssignmentDeclarationKind(node);
|
|
if (assignmentKind === AssignmentDeclarationKind.Prototype || assignmentKind === AssignmentDeclarationKind.PrototypeProperty) {
|
|
const symbol = getSymbolOfNode(node.left);
|
|
if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) {
|
|
node = symbol.parent.valueDeclaration;
|
|
}
|
|
}
|
|
}
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
switch (node.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
case SyntaxKind.JSDocFunctionType:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
case SyntaxKind.JSDocTemplateTag:
|
|
case SyntaxKind.JSDocTypedefTag:
|
|
case SyntaxKind.JSDocEnumTag:
|
|
case SyntaxKind.JSDocCallbackTag:
|
|
case SyntaxKind.MappedType:
|
|
case SyntaxKind.ConditionalType:
|
|
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
|
|
if (node.kind === SyntaxKind.MappedType) {
|
|
return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((<MappedTypeNode>node).typeParameter)));
|
|
}
|
|
else if (node.kind === SyntaxKind.ConditionalType) {
|
|
return concatenate(outerTypeParameters, getInferTypeParameters(<ConditionalTypeNode>node));
|
|
}
|
|
const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(<DeclarationWithTypeParameters>node));
|
|
const thisType = includeThisTypes &&
|
|
(node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) &&
|
|
getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType;
|
|
return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The outer type parameters are those defined by enclosing generic classes, methods, or functions.
|
|
function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined {
|
|
const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!;
|
|
Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations");
|
|
return getOuterTypeParameters(declaration);
|
|
}
|
|
|
|
// The local type parameters are the combined set of type parameters from all declarations of the class,
|
|
// interface, or type alias.
|
|
function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] | undefined {
|
|
let result: TypeParameter[] | undefined;
|
|
for (const node of symbol.declarations) {
|
|
if (node.kind === SyntaxKind.InterfaceDeclaration ||
|
|
node.kind === SyntaxKind.ClassDeclaration ||
|
|
node.kind === SyntaxKind.ClassExpression ||
|
|
isJSConstructor(node) ||
|
|
isTypeAlias(node)) {
|
|
const declaration = <InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag>node;
|
|
result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// The full set of type parameters for a generic class or interface type consists of its outer type parameters plus
|
|
// its locally declared type parameters.
|
|
function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined {
|
|
return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol));
|
|
}
|
|
|
|
// A type is a mixin constructor if it has a single construct signature taking no type parameters and a single
|
|
// rest parameter of type any[].
|
|
function isMixinConstructorType(type: Type) {
|
|
const signatures = getSignaturesOfType(type, SignatureKind.Construct);
|
|
if (signatures.length === 1) {
|
|
const s = signatures[0];
|
|
return !s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s) && getElementTypeOfArrayType(getTypeOfParameter(s.parameters[0])) === anyType;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isConstructorType(type: Type): boolean {
|
|
if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) {
|
|
return true;
|
|
}
|
|
if (type.flags & TypeFlags.TypeVariable) {
|
|
const constraint = getBaseConstraintOfType(type);
|
|
return !!constraint && isMixinConstructorType(constraint);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined {
|
|
return getEffectiveBaseTypeNode(type.symbol.valueDeclaration as ClassLikeDeclaration);
|
|
}
|
|
|
|
function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] {
|
|
const typeArgCount = length(typeArgumentNodes);
|
|
const isJavascript = isInJSFile(location);
|
|
return filter(getSignaturesOfType(type, SignatureKind.Construct),
|
|
sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters));
|
|
}
|
|
|
|
function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] {
|
|
const signatures = getConstructorsForTypeArguments(type, typeArgumentNodes, location);
|
|
const typeArguments = map(typeArgumentNodes, getTypeFromTypeNode);
|
|
return sameMap<Signature>(signatures, sig => some(sig.typeParameters) ? getSignatureInstantiation(sig, typeArguments, isInJSFile(location)) : sig);
|
|
}
|
|
|
|
/**
|
|
* The base constructor of a class can resolve to
|
|
* * undefinedType if the class has no extends clause,
|
|
* * unknownType if an error occurred during resolution of the extends expression,
|
|
* * nullType if the extends expression is the null value,
|
|
* * anyType if the extends expression has type any, or
|
|
* * an object type with at least one construct signature.
|
|
*/
|
|
function getBaseConstructorTypeOfClass(type: InterfaceType): Type {
|
|
if (!type.resolvedBaseConstructorType) {
|
|
const decl = <ClassLikeDeclaration>type.symbol.valueDeclaration;
|
|
const extended = getEffectiveBaseTypeNode(decl);
|
|
const baseTypeNode = getBaseTypeNodeOfClass(type);
|
|
if (!baseTypeNode) {
|
|
return type.resolvedBaseConstructorType = undefinedType;
|
|
}
|
|
if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseConstructorType)) {
|
|
return errorType;
|
|
}
|
|
const baseConstructorType = checkExpression(baseTypeNode.expression);
|
|
if (extended && baseTypeNode !== extended) {
|
|
Debug.assert(!extended.typeArguments); // Because this is in a JS file, and baseTypeNode is in an @extends tag
|
|
checkExpression(extended.expression);
|
|
}
|
|
if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
|
|
// Resolving the members of a class requires us to resolve the base class of that class.
|
|
// We force resolution here such that we catch circularities now.
|
|
resolveStructuredTypeMembers(<ObjectType>baseConstructorType);
|
|
}
|
|
if (!popTypeResolution()) {
|
|
error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol));
|
|
return type.resolvedBaseConstructorType = errorType;
|
|
}
|
|
if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
|
|
const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType));
|
|
if (baseConstructorType.flags & TypeFlags.TypeParameter) {
|
|
const constraint = getConstraintFromTypeParameter(baseConstructorType);
|
|
let ctorReturn: Type = unknownType;
|
|
if (constraint) {
|
|
const ctorSig = getSignaturesOfType(constraint, SignatureKind.Construct);
|
|
if (ctorSig[0]) {
|
|
ctorReturn = getReturnTypeOfSignature(ctorSig[0]);
|
|
}
|
|
}
|
|
addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn)));
|
|
}
|
|
return type.resolvedBaseConstructorType = errorType;
|
|
}
|
|
type.resolvedBaseConstructorType = baseConstructorType;
|
|
}
|
|
return type.resolvedBaseConstructorType;
|
|
}
|
|
|
|
function getImplementsTypes(type: InterfaceType): BaseType[] {
|
|
let resolvedImplementsTypes: BaseType[] = emptyArray;
|
|
for (const declaration of type.symbol.declarations) {
|
|
const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration);
|
|
if (!implementsTypeNodes) continue;
|
|
for (const node of implementsTypeNodes) {
|
|
const implementsType = getTypeFromTypeNode(node);
|
|
if (implementsType !== errorType) {
|
|
if (resolvedImplementsTypes === emptyArray) {
|
|
resolvedImplementsTypes = [<ObjectType>implementsType];
|
|
}
|
|
else {
|
|
resolvedImplementsTypes.push(implementsType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return resolvedImplementsTypes;
|
|
}
|
|
|
|
function getBaseTypes(type: InterfaceType): BaseType[] {
|
|
if (!type.resolvedBaseTypes) {
|
|
if (type.objectFlags & ObjectFlags.Tuple) {
|
|
type.resolvedBaseTypes = [createArrayType(getUnionType(type.typeParameters || emptyArray), (<TupleType>type).readonly)];
|
|
}
|
|
else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
|
|
if (type.symbol.flags & SymbolFlags.Class) {
|
|
resolveBaseTypesOfClass(type);
|
|
}
|
|
if (type.symbol.flags & SymbolFlags.Interface) {
|
|
resolveBaseTypesOfInterface(type);
|
|
}
|
|
}
|
|
else {
|
|
Debug.fail("type must be class or interface");
|
|
}
|
|
}
|
|
return type.resolvedBaseTypes;
|
|
}
|
|
|
|
function resolveBaseTypesOfClass(type: InterfaceType) {
|
|
type.resolvedBaseTypes = resolvingEmptyArray;
|
|
const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type));
|
|
if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) {
|
|
return type.resolvedBaseTypes = emptyArray;
|
|
}
|
|
const baseTypeNode = getBaseTypeNodeOfClass(type)!;
|
|
let baseType: Type;
|
|
const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined;
|
|
if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class &&
|
|
areAllOuterTypeParametersApplied(originalBaseType!)) {
|
|
// When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the
|
|
// class and all return the instance type of the class. There is no need for further checks and we can apply the
|
|
// type arguments in the same manner as a type reference to get the same error reporting experience.
|
|
baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol);
|
|
}
|
|
else if (baseConstructorType.flags & TypeFlags.Any) {
|
|
baseType = baseConstructorType;
|
|
}
|
|
else {
|
|
// The class derives from a "class-like" constructor function, check that we have at least one construct signature
|
|
// with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere
|
|
// we check that all instantiated signatures return the same type.
|
|
const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode);
|
|
if (!constructors.length) {
|
|
error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments);
|
|
return type.resolvedBaseTypes = emptyArray;
|
|
}
|
|
baseType = getReducedType(getReturnTypeOfSignature(constructors[0]));
|
|
}
|
|
|
|
if (baseType === errorType) {
|
|
return type.resolvedBaseTypes = emptyArray;
|
|
}
|
|
if (!isValidBaseType(baseType)) {
|
|
error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(baseType));
|
|
return type.resolvedBaseTypes = emptyArray;
|
|
}
|
|
if (type === baseType || hasBaseType(baseType, type)) {
|
|
error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type,
|
|
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType));
|
|
return type.resolvedBaseTypes = emptyArray;
|
|
}
|
|
if (type.resolvedBaseTypes === resolvingEmptyArray) {
|
|
// Circular reference, likely through instantiation of default parameters
|
|
// (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset
|
|
// as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a
|
|
// partial instantiation of the members without the base types fully resolved
|
|
type.members = undefined;
|
|
}
|
|
return type.resolvedBaseTypes = [baseType];
|
|
}
|
|
|
|
function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType?
|
|
// An unapplied type parameter has its symbol still the same as the matching argument symbol.
|
|
// Since parameters are applied outer-to-inner, only the last outer parameter needs to be checked.
|
|
const outerTypeParameters = (<InterfaceType>type).outerTypeParameters;
|
|
if (outerTypeParameters) {
|
|
const last = outerTypeParameters.length - 1;
|
|
const typeArguments = getTypeArguments(<TypeReference>type);
|
|
return outerTypeParameters[last].symbol !== typeArguments[last].symbol;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// A valid base type is `any`, an object type or intersection of object types.
|
|
function isValidBaseType(type: Type): type is BaseType {
|
|
if (type.flags & TypeFlags.TypeParameter) {
|
|
const constraint = getBaseConstraintOfType(type);
|
|
if (constraint) {
|
|
return isValidBaseType(constraint);
|
|
}
|
|
}
|
|
// TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed?
|
|
// There's no reason a `T` should be allowed while a `Readonly<T>` should not.
|
|
return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) ||
|
|
type.flags & TypeFlags.Intersection && every((<IntersectionType>type).types, isValidBaseType));
|
|
}
|
|
|
|
function resolveBaseTypesOfInterface(type: InterfaceType): void {
|
|
type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray;
|
|
for (const declaration of type.symbol.declarations) {
|
|
if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration)) {
|
|
for (const node of getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration)!) {
|
|
const baseType = getReducedType(getTypeFromTypeNode(node));
|
|
if (baseType !== errorType) {
|
|
if (isValidBaseType(baseType)) {
|
|
if (type !== baseType && !hasBaseType(baseType, type)) {
|
|
if (type.resolvedBaseTypes === emptyArray) {
|
|
type.resolvedBaseTypes = [<ObjectType>baseType];
|
|
}
|
|
else {
|
|
type.resolvedBaseTypes.push(baseType);
|
|
}
|
|
}
|
|
else {
|
|
error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType));
|
|
}
|
|
}
|
|
else {
|
|
error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the interface given by the symbol is free of "this" references.
|
|
*
|
|
* Specifically, the result is true if the interface itself contains no references
|
|
* to "this" in its body, if all base types are interfaces,
|
|
* and if none of the base interfaces have a "this" type.
|
|
*/
|
|
function isThislessInterface(symbol: Symbol): boolean {
|
|
for (const declaration of symbol.declarations) {
|
|
if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
|
|
if (declaration.flags & NodeFlags.ContainsThis) {
|
|
return false;
|
|
}
|
|
const baseTypeNodes = getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration);
|
|
if (baseTypeNodes) {
|
|
for (const node of baseTypeNodes) {
|
|
if (isEntityNameExpression(node.expression)) {
|
|
const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true);
|
|
if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType {
|
|
let links = getSymbolLinks(symbol);
|
|
const originalLinks = links;
|
|
if (!links.declaredType) {
|
|
const kind = symbol.flags & SymbolFlags.Class ? ObjectFlags.Class : ObjectFlags.Interface;
|
|
const merged = mergeJSSymbols(symbol, getAssignedClassSymbol(symbol.valueDeclaration));
|
|
if (merged) {
|
|
// note:we overwrite links because we just cloned the symbol
|
|
symbol = links = merged;
|
|
}
|
|
|
|
const type = originalLinks.declaredType = links.declaredType = <InterfaceType>createObjectType(kind, symbol);
|
|
const outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol);
|
|
const localTypeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
|
|
// A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type
|
|
// because it is not feasible to analyze all members to determine if the "this" type escapes the class (in particular,
|
|
// property types inferred from initializers and method return types inferred from return statements are very hard
|
|
// to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of
|
|
// "this" references.
|
|
if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) {
|
|
type.objectFlags |= ObjectFlags.Reference;
|
|
type.typeParameters = concatenate(outerTypeParameters, localTypeParameters);
|
|
type.outerTypeParameters = outerTypeParameters;
|
|
type.localTypeParameters = localTypeParameters;
|
|
(<GenericType>type).instantiations = createMap<TypeReference>();
|
|
(<GenericType>type).instantiations.set(getTypeListId(type.typeParameters), <GenericType>type);
|
|
(<GenericType>type).target = <GenericType>type;
|
|
(<GenericType>type).resolvedTypeArguments = type.typeParameters;
|
|
type.thisType = createTypeParameter(symbol);
|
|
type.thisType.isThisType = true;
|
|
type.thisType.constraint = type;
|
|
}
|
|
}
|
|
return <InterfaceType>links.declaredType;
|
|
}
|
|
|
|
function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.declaredType) {
|
|
// Note that we use the links object as the target here because the symbol object is used as the unique
|
|
// identity for resolution of the 'type' property in SymbolLinks.
|
|
if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) {
|
|
return errorType;
|
|
}
|
|
|
|
const declaration = Debug.checkDefined(find(symbol.declarations, isTypeAlias), "Type alias symbol with no valid declaration found");
|
|
const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type;
|
|
// If typeNode is missing, we will error in checkJSDocTypedefTag.
|
|
let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType;
|
|
|
|
if (popTypeResolution()) {
|
|
const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
|
|
if (typeParameters) {
|
|
// Initialize the instantiation cache for generic type aliases. The declared type corresponds to
|
|
// an instantiation of the type alias with the type parameters supplied as type arguments.
|
|
links.typeParameters = typeParameters;
|
|
links.instantiations = createMap<Type>();
|
|
links.instantiations.set(getTypeListId(typeParameters), type);
|
|
}
|
|
}
|
|
else {
|
|
type = errorType;
|
|
error(isNamedDeclaration(declaration) ? declaration.name : declaration || declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol));
|
|
}
|
|
links.declaredType = type;
|
|
}
|
|
return links.declaredType;
|
|
}
|
|
|
|
function isStringConcatExpression(expr: Node): boolean {
|
|
if (isStringLiteralLike(expr)) {
|
|
return true;
|
|
}
|
|
else if (expr.kind === SyntaxKind.BinaryExpression) {
|
|
return isStringConcatExpression((<BinaryExpression>expr).left) && isStringConcatExpression((<BinaryExpression>expr).right);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isLiteralEnumMember(member: EnumMember) {
|
|
const expr = member.initializer;
|
|
if (!expr) {
|
|
return !(member.flags & NodeFlags.Ambient);
|
|
}
|
|
switch (expr.kind) {
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
return true;
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
return (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
|
|
(<PrefixUnaryExpression>expr).operand.kind === SyntaxKind.NumericLiteral;
|
|
case SyntaxKind.Identifier:
|
|
return nodeIsMissing(expr) || !!getSymbolOfNode(member.parent).exports!.get((<Identifier>expr).escapedText);
|
|
case SyntaxKind.BinaryExpression:
|
|
return isStringConcatExpression(expr);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getEnumKind(symbol: Symbol): EnumKind {
|
|
const links = getSymbolLinks(symbol);
|
|
if (links.enumKind !== undefined) {
|
|
return links.enumKind;
|
|
}
|
|
let hasNonLiteralMember = false;
|
|
for (const declaration of symbol.declarations) {
|
|
if (declaration.kind === SyntaxKind.EnumDeclaration) {
|
|
for (const member of (<EnumDeclaration>declaration).members) {
|
|
if (member.initializer && isStringLiteralLike(member.initializer)) {
|
|
return links.enumKind = EnumKind.Literal;
|
|
}
|
|
if (!isLiteralEnumMember(member)) {
|
|
hasNonLiteralMember = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return links.enumKind = hasNonLiteralMember ? EnumKind.Numeric : EnumKind.Literal;
|
|
}
|
|
|
|
function getBaseTypeOfEnumLiteralType(type: Type) {
|
|
return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type;
|
|
}
|
|
|
|
function getDeclaredTypeOfEnum(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
if (links.declaredType) {
|
|
return links.declaredType;
|
|
}
|
|
if (getEnumKind(symbol) === EnumKind.Literal) {
|
|
enumCount++;
|
|
const memberTypeList: Type[] = [];
|
|
for (const declaration of symbol.declarations) {
|
|
if (declaration.kind === SyntaxKind.EnumDeclaration) {
|
|
for (const member of (<EnumDeclaration>declaration).members) {
|
|
const value = getEnumMemberValue(member);
|
|
const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member)));
|
|
getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType;
|
|
memberTypeList.push(getRegularTypeOfLiteralType(memberType));
|
|
}
|
|
}
|
|
}
|
|
if (memberTypeList.length) {
|
|
const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined);
|
|
if (enumType.flags & TypeFlags.Union) {
|
|
enumType.flags |= TypeFlags.EnumLiteral;
|
|
enumType.symbol = symbol;
|
|
}
|
|
return links.declaredType = enumType;
|
|
}
|
|
}
|
|
const enumType = createType(TypeFlags.Enum);
|
|
enumType.symbol = symbol;
|
|
return links.declaredType = enumType;
|
|
}
|
|
|
|
function getDeclaredTypeOfEnumMember(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.declaredType) {
|
|
const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!);
|
|
if (!links.declaredType) {
|
|
links.declaredType = enumType;
|
|
}
|
|
}
|
|
return links.declaredType;
|
|
}
|
|
|
|
function getDeclaredTypeOfTypeParameter(symbol: Symbol): TypeParameter {
|
|
const links = getSymbolLinks(symbol);
|
|
return links.declaredType || (links.declaredType = createTypeParameter(symbol));
|
|
}
|
|
|
|
function getDeclaredTypeOfAlias(symbol: Symbol): Type {
|
|
const links = getSymbolLinks(symbol);
|
|
return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol)));
|
|
}
|
|
|
|
function getDeclaredTypeOfSymbol(symbol: Symbol): Type {
|
|
return tryGetDeclaredTypeOfSymbol(symbol) || errorType;
|
|
}
|
|
|
|
function tryGetDeclaredTypeOfSymbol(symbol: Symbol): Type | undefined {
|
|
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
|
|
return getDeclaredTypeOfClassOrInterface(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.TypeAlias) {
|
|
return getDeclaredTypeOfTypeAlias(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.TypeParameter) {
|
|
return getDeclaredTypeOfTypeParameter(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Enum) {
|
|
return getDeclaredTypeOfEnum(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.EnumMember) {
|
|
return getDeclaredTypeOfEnumMember(symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.Alias) {
|
|
return getDeclaredTypeOfAlias(symbol);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string
|
|
* literal type, an array with an element type that is free of this references, or a type reference that is
|
|
* free of this references.
|
|
*/
|
|
function isThislessType(node: TypeNode): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.AnyKeyword:
|
|
case SyntaxKind.UnknownKeyword:
|
|
case SyntaxKind.StringKeyword:
|
|
case SyntaxKind.NumberKeyword:
|
|
case SyntaxKind.BigIntKeyword:
|
|
case SyntaxKind.BooleanKeyword:
|
|
case SyntaxKind.SymbolKeyword:
|
|
case SyntaxKind.ObjectKeyword:
|
|
case SyntaxKind.VoidKeyword:
|
|
case SyntaxKind.UndefinedKeyword:
|
|
case SyntaxKind.NullKeyword:
|
|
case SyntaxKind.NeverKeyword:
|
|
case SyntaxKind.LiteralType:
|
|
return true;
|
|
case SyntaxKind.ArrayType:
|
|
return isThislessType((<ArrayTypeNode>node).elementType);
|
|
case SyntaxKind.TypeReference:
|
|
return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments!.every(isThislessType);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** A type parameter is thisless if its constraint is thisless, or if it has no constraint. */
|
|
function isThislessTypeParameter(node: TypeParameterDeclaration) {
|
|
const constraint = getEffectiveConstraintOfTypeParameter(node);
|
|
return !constraint || isThislessType(constraint);
|
|
}
|
|
|
|
/**
|
|
* A variable-like declaration is free of this references if it has a type annotation
|
|
* that is thisless, or if it has no type annotation and no initializer (and is thus of type any).
|
|
*/
|
|
function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean {
|
|
const typeNode = getEffectiveTypeAnnotationNode(node);
|
|
return typeNode ? isThislessType(typeNode) : !hasInitializer(node);
|
|
}
|
|
|
|
/**
|
|
* A function-like declaration is considered free of `this` references if it has a return type
|
|
* annotation that is free of this references and if each parameter is thisless and if
|
|
* each type parameter (if present) is thisless.
|
|
*/
|
|
function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
|
|
const returnType = getEffectiveReturnTypeNode(node);
|
|
const typeParameters = getEffectiveTypeParameterDeclarations(node);
|
|
return (node.kind === SyntaxKind.Constructor || (!!returnType && isThislessType(returnType))) &&
|
|
node.parameters.every(isThislessVariableLikeDeclaration) &&
|
|
typeParameters.every(isThislessTypeParameter);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the class or interface member given by the symbol is free of "this" references. The
|
|
* function may return false for symbols that are actually free of "this" references because it is not
|
|
* feasible to perform a complete analysis in all cases. In particular, property members with types
|
|
* inferred from their initializers and function members with inferred return types are conservatively
|
|
* assumed not to be free of "this" references.
|
|
*/
|
|
function isThisless(symbol: Symbol): boolean {
|
|
if (symbol.declarations && symbol.declarations.length === 1) {
|
|
const declaration = symbol.declarations[0];
|
|
if (declaration) {
|
|
switch (declaration.kind) {
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
return isThislessVariableLikeDeclaration(<VariableLikeDeclaration>declaration);
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
return isThislessFunctionLikeDeclaration(<FunctionLikeDeclaration | AccessorDeclaration>declaration);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true,
|
|
// we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation.
|
|
function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable {
|
|
const result = createSymbolTable();
|
|
for (const symbol of symbols) {
|
|
result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) {
|
|
for (const s of baseSymbols) {
|
|
if (!symbols.has(s.escapedName) && !isStaticPrivateIdentifierProperty(s)) {
|
|
symbols.set(s.escapedName, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isStaticPrivateIdentifierProperty(s: Symbol): boolean {
|
|
return !!s.valueDeclaration && isPrivateIdentifierPropertyDeclaration(s.valueDeclaration) && hasModifier(s.valueDeclaration, ModifierFlags.Static);
|
|
}
|
|
|
|
function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers {
|
|
if (!(<InterfaceTypeWithDeclaredMembers>type).declaredProperties) {
|
|
const symbol = type.symbol;
|
|
const members = getMembersOfSymbol(symbol);
|
|
(<InterfaceTypeWithDeclaredMembers>type).declaredProperties = getNamedMembers(members);
|
|
// Start with signatures at empty array in case of recursive types
|
|
(<InterfaceTypeWithDeclaredMembers>type).declaredCallSignatures = emptyArray;
|
|
(<InterfaceTypeWithDeclaredMembers>type).declaredConstructSignatures = emptyArray;
|
|
|
|
(<InterfaceTypeWithDeclaredMembers>type).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call));
|
|
(<InterfaceTypeWithDeclaredMembers>type).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New));
|
|
(<InterfaceTypeWithDeclaredMembers>type).declaredStringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String);
|
|
(<InterfaceTypeWithDeclaredMembers>type).declaredNumberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number);
|
|
}
|
|
return <InterfaceTypeWithDeclaredMembers>type;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether a type can be used as a property name.
|
|
*/
|
|
function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType {
|
|
return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique);
|
|
}
|
|
|
|
/**
|
|
* Indicates whether a declaration name is definitely late-bindable.
|
|
* A declaration name is only late-bindable if:
|
|
* - It is a `ComputedPropertyName`.
|
|
* - Its expression is an `Identifier` or either a `PropertyAccessExpression` an
|
|
* `ElementAccessExpression` consisting only of these same three types of nodes.
|
|
* - The type of its expression is a string or numeric literal type, or is a `unique symbol` type.
|
|
*/
|
|
function isLateBindableName(node: DeclarationName): node is LateBoundName {
|
|
if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) {
|
|
return false;
|
|
}
|
|
const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
|
|
return isEntityNameExpression(expr)
|
|
&& isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr));
|
|
}
|
|
|
|
function isLateBoundName(name: __String): boolean {
|
|
return (name as string).charCodeAt(0) === CharacterCodes._ &&
|
|
(name as string).charCodeAt(1) === CharacterCodes._ &&
|
|
(name as string).charCodeAt(2) === CharacterCodes.at;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether a declaration has a late-bindable dynamic name.
|
|
*/
|
|
function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration {
|
|
const name = getNameOfDeclaration(node);
|
|
return !!name && isLateBindableName(name);
|
|
}
|
|
|
|
/**
|
|
* Indicates whether a declaration has a dynamic name that cannot be late-bound.
|
|
*/
|
|
function hasNonBindableDynamicName(node: Declaration) {
|
|
return hasDynamicName(node) && !hasLateBindableName(node);
|
|
}
|
|
|
|
/**
|
|
* Indicates whether a declaration name is a dynamic name that cannot be late-bound.
|
|
*/
|
|
function isNonBindableDynamicName(node: DeclarationName) {
|
|
return isDynamicName(node) && !isLateBindableName(node);
|
|
}
|
|
|
|
/**
|
|
* Gets the symbolic name for a member from its type.
|
|
*/
|
|
function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String {
|
|
if (type.flags & TypeFlags.UniqueESSymbol) {
|
|
return (<UniqueESSymbolType>type).escapedName;
|
|
}
|
|
if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
|
|
return escapeLeadingUnderscores("" + (<StringLiteralType | NumberLiteralType>type).value);
|
|
}
|
|
return Debug.fail();
|
|
}
|
|
|
|
/**
|
|
* Adds a declaration to a late-bound dynamic member. This performs the same function for
|
|
* late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound
|
|
* members.
|
|
*/
|
|
function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) {
|
|
Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol.");
|
|
symbol.flags |= symbolFlags;
|
|
getSymbolLinks(member.symbol).lateSymbol = symbol;
|
|
if (!symbol.declarations) {
|
|
symbol.declarations = [member];
|
|
}
|
|
else {
|
|
symbol.declarations.push(member);
|
|
}
|
|
if (symbolFlags & SymbolFlags.Value) {
|
|
if (!symbol.valueDeclaration || symbol.valueDeclaration.kind !== member.kind) {
|
|
symbol.valueDeclaration = member;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs late-binding of a dynamic member. This performs the same function for
|
|
* late-bound members that `declareSymbol` in binder.ts performs for early-bound
|
|
* members.
|
|
*
|
|
* If a symbol is a dynamic name from a computed property, we perform an additional "late"
|
|
* binding phase to attempt to resolve the name for the symbol from the type of the computed
|
|
* property's expression. If the type of the expression is a string-literal, numeric-literal,
|
|
* or unique symbol type, we can use that type as the name of the symbol.
|
|
*
|
|
* For example, given:
|
|
*
|
|
* const x = Symbol();
|
|
*
|
|
* interface I {
|
|
* [x]: number;
|
|
* }
|
|
*
|
|
* The binder gives the property `[x]: number` a special symbol with the name "__computed".
|
|
* In the late-binding phase we can type-check the expression `x` and see that it has a
|
|
* unique symbol type which we can then use as the name of the member. This allows users
|
|
* to define custom symbols that can be used in the members of an object type.
|
|
*
|
|
* @param parent The containing symbol for the member.
|
|
* @param earlySymbols The early-bound symbols of the parent.
|
|
* @param lateSymbols The late-bound symbols of the parent.
|
|
* @param decl The member to bind.
|
|
*/
|
|
function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: UnderscoreEscapedMap<TransientSymbol>, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) {
|
|
Debug.assert(!!decl.symbol, "The member is expected to have a symbol.");
|
|
const links = getNodeLinks(decl);
|
|
if (!links.resolvedSymbol) {
|
|
// In the event we attempt to resolve the late-bound name of this member recursively,
|
|
// fall back to the early-bound name of this member.
|
|
links.resolvedSymbol = decl.symbol;
|
|
const declName = isBinaryExpression(decl) ? decl.left : decl.name;
|
|
const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName);
|
|
if (isTypeUsableAsPropertyName(type)) {
|
|
const memberName = getPropertyNameFromType(type);
|
|
const symbolFlags = decl.symbol.flags;
|
|
|
|
// Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations.
|
|
let lateSymbol = lateSymbols.get(memberName);
|
|
if (!lateSymbol) lateSymbols.set(memberName, lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late));
|
|
|
|
// Report an error if a late-bound member has the same name as an early-bound member,
|
|
// or if we have another early-bound symbol declaration with the same name and
|
|
// conflicting flags.
|
|
const earlySymbol = earlySymbols && earlySymbols.get(memberName);
|
|
if (lateSymbol.flags & getExcludedSymbolFlags(symbolFlags) || earlySymbol) {
|
|
// If we have an existing early-bound member, combine its declarations so that we can
|
|
// report an error at each declaration.
|
|
const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations;
|
|
const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName);
|
|
forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name));
|
|
error(declName || decl, Diagnostics.Duplicate_property_0, name);
|
|
lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late);
|
|
}
|
|
lateSymbol.nameType = type;
|
|
addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags);
|
|
if (lateSymbol.parent) {
|
|
Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one");
|
|
}
|
|
else {
|
|
lateSymbol.parent = parent;
|
|
}
|
|
return links.resolvedSymbol = lateSymbol;
|
|
}
|
|
}
|
|
return links.resolvedSymbol;
|
|
}
|
|
|
|
function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap<Symbol> {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links[resolutionKind]) {
|
|
const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports;
|
|
const earlySymbols = !isStatic ? symbol.members :
|
|
symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) :
|
|
symbol.exports;
|
|
|
|
// In the event we recursively resolve the members/exports of the symbol, we
|
|
// set the initial value of resolvedMembers/resolvedExports to the early-bound
|
|
// members/exports of the symbol.
|
|
links[resolutionKind] = earlySymbols || emptySymbols;
|
|
|
|
// fill in any as-yet-unresolved late-bound members.
|
|
const lateSymbols = createSymbolTable() as UnderscoreEscapedMap<TransientSymbol>;
|
|
for (const decl of symbol.declarations) {
|
|
const members = getMembersOfDeclaration(decl);
|
|
if (members) {
|
|
for (const member of members) {
|
|
if (isStatic === hasStaticModifier(member) && hasLateBindableName(member)) {
|
|
lateBindMember(symbol, earlySymbols, lateSymbols, member);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const assignments = symbol.assignmentDeclarationMembers;
|
|
if (assignments) {
|
|
const decls = arrayFrom(assignments.values());
|
|
for (const member of decls) {
|
|
const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression);
|
|
const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty
|
|
|| assignmentKind === AssignmentDeclarationKind.ThisProperty
|
|
|| assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty
|
|
|| assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name
|
|
if (isStatic === !isInstanceMember && hasLateBindableName(member)) {
|
|
lateBindMember(symbol, earlySymbols, lateSymbols, member);
|
|
}
|
|
}
|
|
}
|
|
|
|
links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols;
|
|
}
|
|
|
|
return links[resolutionKind]!;
|
|
}
|
|
|
|
/**
|
|
* Gets a SymbolTable containing both the early- and late-bound members of a symbol.
|
|
*
|
|
* For a description of late-binding, see `lateBindMember`.
|
|
*/
|
|
function getMembersOfSymbol(symbol: Symbol) {
|
|
return symbol.flags & SymbolFlags.LateBindingContainer
|
|
? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedMembers)
|
|
: symbol.members || emptySymbols;
|
|
}
|
|
|
|
/**
|
|
* If a symbol is the dynamic name of the member of an object type, get the late-bound
|
|
* symbol of the member.
|
|
*
|
|
* For a description of late-binding, see `lateBindMember`.
|
|
*/
|
|
function getLateBoundSymbol(symbol: Symbol): Symbol {
|
|
if (symbol.flags & SymbolFlags.ClassMember && symbol.escapedName === InternalSymbolName.Computed) {
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) {
|
|
// force late binding of members/exports. This will set the late-bound symbol
|
|
const parent = getMergedSymbol(symbol.parent)!;
|
|
if (some(symbol.declarations, hasStaticModifier)) {
|
|
getExportsOfSymbol(parent);
|
|
}
|
|
else {
|
|
getMembersOfSymbol(parent);
|
|
}
|
|
}
|
|
return links.lateSymbol || (links.lateSymbol = symbol);
|
|
}
|
|
return symbol;
|
|
}
|
|
|
|
function getTypeWithThisArgument(type: Type, thisArgument?: Type, needApparentType?: boolean): Type {
|
|
if (getObjectFlags(type) & ObjectFlags.Reference) {
|
|
const target = (<TypeReference>type).target;
|
|
const typeArguments = getTypeArguments(<TypeReference>type);
|
|
if (length(target.typeParameters) === length(typeArguments)) {
|
|
const ref = createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!]));
|
|
return needApparentType ? getApparentType(ref) : ref;
|
|
}
|
|
}
|
|
else if (type.flags & TypeFlags.Intersection) {
|
|
return getIntersectionType(map((<IntersectionType>type).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType)));
|
|
}
|
|
return needApparentType ? getApparentType(type) : type;
|
|
}
|
|
|
|
function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) {
|
|
let mapper: TypeMapper | undefined;
|
|
let members: SymbolTable;
|
|
let callSignatures: readonly Signature[];
|
|
let constructSignatures: readonly Signature[] | undefined;
|
|
let stringIndexInfo: IndexInfo | undefined;
|
|
let numberIndexInfo: IndexInfo | undefined;
|
|
if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) {
|
|
members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties);
|
|
callSignatures = source.declaredCallSignatures;
|
|
constructSignatures = source.declaredConstructSignatures;
|
|
stringIndexInfo = source.declaredStringIndexInfo;
|
|
numberIndexInfo = source.declaredNumberIndexInfo;
|
|
}
|
|
else {
|
|
mapper = createTypeMapper(typeParameters, typeArguments);
|
|
members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1);
|
|
callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper);
|
|
constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper);
|
|
stringIndexInfo = instantiateIndexInfo(source.declaredStringIndexInfo, mapper);
|
|
numberIndexInfo = instantiateIndexInfo(source.declaredNumberIndexInfo, mapper);
|
|
}
|
|
const baseTypes = getBaseTypes(source);
|
|
if (baseTypes.length) {
|
|
if (source.symbol && members === getMembersOfSymbol(source.symbol)) {
|
|
members = createSymbolTable(source.declaredProperties);
|
|
}
|
|
setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
|
|
const thisArgument = lastOrUndefined(typeArguments);
|
|
for (const baseType of baseTypes) {
|
|
const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType;
|
|
addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType));
|
|
callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call));
|
|
constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct));
|
|
if (!stringIndexInfo) {
|
|
stringIndexInfo = instantiatedBaseType === anyType ?
|
|
createIndexInfo(anyType, /*isReadonly*/ false) :
|
|
getIndexInfoOfType(instantiatedBaseType, IndexKind.String);
|
|
}
|
|
numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number);
|
|
}
|
|
}
|
|
setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
|
|
}
|
|
|
|
function resolveClassOrInterfaceMembers(type: InterfaceType): void {
|
|
resolveObjectTypeMembers(type, resolveDeclaredMembers(type), emptyArray, emptyArray);
|
|
}
|
|
|
|
function resolveTypeReferenceMembers(type: TypeReference): void {
|
|
const source = resolveDeclaredMembers(type.target);
|
|
const typeParameters = concatenate(source.typeParameters!, [source.thisType!]);
|
|
const typeArguments = getTypeArguments(type);
|
|
const paddedTypeArguments = typeArguments.length === typeParameters.length ? typeArguments : concatenate(typeArguments, [type]);
|
|
resolveObjectTypeMembers(type, source, typeParameters, paddedTypeArguments);
|
|
}
|
|
|
|
function createSignature(
|
|
declaration: SignatureDeclaration | JSDocSignature | undefined,
|
|
typeParameters: readonly TypeParameter[] | undefined,
|
|
thisParameter: Symbol | undefined,
|
|
parameters: readonly Symbol[],
|
|
resolvedReturnType: Type | undefined,
|
|
resolvedTypePredicate: TypePredicate | undefined,
|
|
minArgumentCount: number,
|
|
flags: SignatureFlags
|
|
): Signature {
|
|
const sig = new Signature(checker, flags);
|
|
sig.declaration = declaration;
|
|
sig.typeParameters = typeParameters;
|
|
sig.parameters = parameters;
|
|
sig.thisParameter = thisParameter;
|
|
sig.resolvedReturnType = resolvedReturnType;
|
|
sig.resolvedTypePredicate = resolvedTypePredicate;
|
|
sig.minArgumentCount = minArgumentCount;
|
|
sig.target = undefined;
|
|
sig.mapper = undefined;
|
|
sig.unionSignatures = undefined;
|
|
return sig;
|
|
}
|
|
|
|
function cloneSignature(sig: Signature): Signature {
|
|
const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined,
|
|
/*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags);
|
|
result.target = sig.target;
|
|
result.mapper = sig.mapper;
|
|
result.unionSignatures = sig.unionSignatures;
|
|
return result;
|
|
}
|
|
|
|
function createUnionSignature(signature: Signature, unionSignatures: Signature[]) {
|
|
const result = cloneSignature(signature);
|
|
result.unionSignatures = unionSignatures;
|
|
result.target = undefined;
|
|
result.mapper = undefined;
|
|
return result;
|
|
}
|
|
|
|
function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature {
|
|
if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) {
|
|
return signature;
|
|
}
|
|
if (!signature.optionalCallSignatureCache) {
|
|
signature.optionalCallSignatureCache = {};
|
|
}
|
|
const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer";
|
|
return signature.optionalCallSignatureCache[key]
|
|
|| (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags));
|
|
}
|
|
|
|
function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) {
|
|
Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain,
|
|
"An optional call signature can either be for an inner call chain or an outer call chain, but not both.");
|
|
const result = cloneSignature(signature);
|
|
result.flags |= callChainFlags;
|
|
return result;
|
|
}
|
|
|
|
function getExpandedParameters(sig: Signature): readonly Symbol[] {
|
|
if (signatureHasRestParameter(sig)) {
|
|
const restIndex = sig.parameters.length - 1;
|
|
const restParameter = sig.parameters[restIndex];
|
|
const restType = getTypeOfSymbol(restParameter);
|
|
if (isTupleType(restType)) {
|
|
const elementTypes = getTypeArguments(restType);
|
|
const minLength = restType.target.minLength;
|
|
const tupleRestIndex = restType.target.hasRestElement ? elementTypes.length - 1 : -1;
|
|
const restParams = map(elementTypes, (t, i) => {
|
|
const name = getParameterNameAtPosition(sig, restIndex + i);
|
|
const checkFlags = i === tupleRestIndex ? CheckFlags.RestParameter :
|
|
i >= minLength ? CheckFlags.OptionalParameter : 0;
|
|
const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags);
|
|
symbol.type = i === tupleRestIndex ? createArrayType(t) : t;
|
|
return symbol;
|
|
});
|
|
return concatenate(sig.parameters.slice(0, restIndex), restParams);
|
|
}
|
|
}
|
|
return sig.parameters;
|
|
}
|
|
|
|
function getDefaultConstructSignatures(classType: InterfaceType): Signature[] {
|
|
const baseConstructorType = getBaseConstructorTypeOfClass(classType);
|
|
const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct);
|
|
if (baseSignatures.length === 0) {
|
|
return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None)];
|
|
}
|
|
const baseTypeNode = getBaseTypeNodeOfClass(classType)!;
|
|
const isJavaScript = isInJSFile(baseTypeNode);
|
|
const typeArguments = typeArgumentsFromTypeReferenceNode(baseTypeNode);
|
|
const typeArgCount = length(typeArguments);
|
|
const result: Signature[] = [];
|
|
for (const baseSig of baseSignatures) {
|
|
const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters);
|
|
const typeParamCount = length(baseSig.typeParameters);
|
|
if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) {
|
|
const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig);
|
|
sig.typeParameters = classType.localTypeParameters;
|
|
sig.resolvedReturnType = classType;
|
|
result.push(sig);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function findMatchingSignature(signatureList: readonly Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined {
|
|
for (const s of signatureList) {
|
|
if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) {
|
|
return s;
|
|
}
|
|
}
|
|
}
|
|
|
|
function findMatchingSignatures(signatureLists: readonly (readonly Signature[])[], signature: Signature, listIndex: number): Signature[] | undefined {
|
|
if (signature.typeParameters) {
|
|
// We require an exact match for generic signatures, so we only return signatures from the first
|
|
// signature list and only if they have exact matches in the other signature lists.
|
|
if (listIndex > 0) {
|
|
return undefined;
|
|
}
|
|
for (let i = 1; i < signatureLists.length; i++) {
|
|
if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) {
|
|
return undefined;
|
|
}
|
|
}
|
|
return [signature];
|
|
}
|
|
let result: Signature[] | undefined;
|
|
for (let i = 0; i < signatureLists.length; i++) {
|
|
// Allow matching non-generic signatures to have excess parameters and different return types.
|
|
// Prefer matching this types if possible.
|
|
const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true);
|
|
if (!match) {
|
|
return undefined;
|
|
}
|
|
result = appendIfUnique(result, match);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// The signatures of a union type are those signatures that are present in each of the constituent types.
|
|
// Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional
|
|
// parameters and may differ in return types. When signatures differ in return types, the resulting return
|
|
// type is the union of the constituent return types.
|
|
function getUnionSignatures(signatureLists: readonly (readonly Signature[])[]): Signature[] {
|
|
let result: Signature[] | undefined;
|
|
let indexWithLengthOverOne: number | undefined;
|
|
for (let i = 0; i < signatureLists.length; i++) {
|
|
if (signatureLists[i].length === 0) return emptyArray;
|
|
if (signatureLists[i].length > 1) {
|
|
indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets
|
|
}
|
|
for (const signature of signatureLists[i]) {
|
|
// Only process signatures with parameter lists that aren't already in the result list
|
|
if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) {
|
|
const unionSignatures = findMatchingSignatures(signatureLists, signature, i);
|
|
if (unionSignatures) {
|
|
let s = signature;
|
|
// Union the result types when more than one signature matches
|
|
if (unionSignatures.length > 1) {
|
|
let thisParameter = signature.thisParameter;
|
|
const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter);
|
|
if (firstThisParameterOfUnionSignatures) {
|
|
const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter)));
|
|
thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType);
|
|
}
|
|
s = createUnionSignature(signature, unionSignatures);
|
|
s.thisParameter = thisParameter;
|
|
}
|
|
(result || (result = [])).push(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!length(result) && indexWithLengthOverOne !== -1) {
|
|
// No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single
|
|
// signature that handles all over them. We only do this when there are overloads in only one constituent.
|
|
// (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of
|
|
// signatures from the type, whose ordering would be non-obvious)
|
|
const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0];
|
|
let results: Signature[] | undefined = masterList.slice();
|
|
for (const signatures of signatureLists) {
|
|
if (signatures !== masterList) {
|
|
const signature = signatures[0];
|
|
Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass");
|
|
results = signature.typeParameters && some(results, s => !!s.typeParameters) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature));
|
|
if (!results) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
result = results;
|
|
}
|
|
return result || emptyArray;
|
|
}
|
|
|
|
function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined): Symbol | undefined {
|
|
if (!left || !right) {
|
|
return left || right;
|
|
}
|
|
// A signature `this` type might be a read or a write position... It's very possible that it should be invariant
|
|
// and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be
|
|
// permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures.
|
|
const thisType = getIntersectionType([getTypeOfSymbol(left), getTypeOfSymbol(right)]);
|
|
return createSymbolWithType(left, thisType);
|
|
}
|
|
|
|
function combineUnionParameters(left: Signature, right: Signature) {
|
|
const leftCount = getParameterCount(left);
|
|
const rightCount = getParameterCount(right);
|
|
const longest = leftCount >= rightCount ? left : right;
|
|
const shorter = longest === left ? right : left;
|
|
const longestCount = longest === left ? leftCount : rightCount;
|
|
const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right));
|
|
const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest);
|
|
const params = new Array<Symbol>(longestCount + (needsExtraRestElement ? 1 : 0));
|
|
for (let i = 0; i < longestCount; i++) {
|
|
const longestParamType = tryGetTypeAtPosition(longest, i)!;
|
|
const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType;
|
|
const unionParamType = getIntersectionType([longestParamType, shorterParamType]);
|
|
const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1);
|
|
const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter);
|
|
const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i);
|
|
const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i);
|
|
|
|
const paramName = leftName === rightName ? leftName :
|
|
!leftName ? rightName :
|
|
!rightName ? leftName :
|
|
undefined;
|
|
const paramSymbol = createSymbol(
|
|
SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0),
|
|
paramName || `arg${i}` as __String
|
|
);
|
|
paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType;
|
|
params[i] = paramSymbol;
|
|
}
|
|
if (needsExtraRestElement) {
|
|
const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String);
|
|
restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount));
|
|
params[longestCount] = restParamSymbol;
|
|
}
|
|
return params;
|
|
}
|
|
|
|
function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature {
|
|
const declaration = left.declaration;
|
|
const params = combineUnionParameters(left, right);
|
|
const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter);
|
|
const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount);
|
|
const result = createSignature(
|
|
declaration,
|
|
left.typeParameters || right.typeParameters,
|
|
thisParam,
|
|
params,
|
|
/*resolvedReturnType*/ undefined,
|
|
/*resolvedTypePredicate*/ undefined,
|
|
minArgCount,
|
|
(left.flags | right.flags) & SignatureFlags.PropagatingFlags
|
|
);
|
|
result.unionSignatures = concatenate(left.unionSignatures || [left], [right]);
|
|
return result;
|
|
}
|
|
|
|
function getUnionIndexInfo(types: readonly Type[], kind: IndexKind): IndexInfo | undefined {
|
|
const indexTypes: Type[] = [];
|
|
let isAnyReadonly = false;
|
|
for (const type of types) {
|
|
const indexInfo = getIndexInfoOfType(type, kind);
|
|
if (!indexInfo) {
|
|
return undefined;
|
|
}
|
|
indexTypes.push(indexInfo.type);
|
|
isAnyReadonly = isAnyReadonly || indexInfo.isReadonly;
|
|
}
|
|
return createIndexInfo(getUnionType(indexTypes, UnionReduction.Subtype), isAnyReadonly);
|
|
}
|
|
|
|
function resolveUnionTypeMembers(type: UnionType) {
|
|
// The members and properties collections are empty for union types. To get all properties of a union
|
|
// type use getPropertiesOfType (only the language service uses this).
|
|
const callSignatures = getUnionSignatures(map(type.types, t => t === globalFunctionType ? [unknownSignature] : getSignaturesOfType(t, SignatureKind.Call)));
|
|
const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct)));
|
|
const stringIndexInfo = getUnionIndexInfo(type.types, IndexKind.String);
|
|
const numberIndexInfo = getUnionIndexInfo(type.types, IndexKind.Number);
|
|
setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
|
|
}
|
|
|
|
function intersectTypes(type1: Type, type2: Type): Type;
|
|
function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined;
|
|
function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined {
|
|
return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]);
|
|
}
|
|
|
|
function intersectIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined {
|
|
return !info1 ? info2 : !info2 ? info1 : createIndexInfo(
|
|
getIntersectionType([info1.type, info2.type]), info1.isReadonly && info2.isReadonly);
|
|
}
|
|
|
|
function unionSpreadIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined {
|
|
return info1 && info2 && createIndexInfo(
|
|
getUnionType([info1.type, info2.type]), info1.isReadonly || info2.isReadonly);
|
|
}
|
|
|
|
function findMixins(types: readonly Type[]): readonly boolean[] {
|
|
const constructorTypeCount = countWhere(types, (t) => getSignaturesOfType(t, SignatureKind.Construct).length > 0);
|
|
const mixinFlags = map(types, isMixinConstructorType);
|
|
if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, (b) => b)) {
|
|
const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true);
|
|
mixinFlags[firstMixinIndex] = false;
|
|
}
|
|
return mixinFlags;
|
|
}
|
|
|
|
function includeMixinType(type: Type, types: readonly Type[], mixinFlags: readonly boolean[], index: number): Type {
|
|
const mixedTypes: Type[] = [];
|
|
for (let i = 0; i < types.length; i++) {
|
|
if (i === index) {
|
|
mixedTypes.push(type);
|
|
}
|
|
else if (mixinFlags[i]) {
|
|
mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0]));
|
|
}
|
|
}
|
|
return getIntersectionType(mixedTypes);
|
|
}
|
|
|
|
function resolveIntersectionTypeMembers(type: IntersectionType) {
|
|
// The members and properties collections are empty for intersection types. To get all properties of an
|
|
// intersection type use getPropertiesOfType (only the language service uses this).
|
|
let callSignatures: Signature[] | undefined;
|
|
let constructSignatures: Signature[] | undefined;
|
|
let stringIndexInfo: IndexInfo | undefined;
|
|
let numberIndexInfo: IndexInfo | undefined;
|
|
const types = type.types;
|
|
const mixinFlags = findMixins(types);
|
|
const mixinCount = countWhere(mixinFlags, (b) => b);
|
|
for (let i = 0; i < types.length; i++) {
|
|
const t = type.types[i];
|
|
// When an intersection type contains mixin constructor types, the construct signatures from
|
|
// those types are discarded and their return types are mixed into the return types of all
|
|
// other construct signatures in the intersection type. For example, the intersection type
|
|
// '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature
|
|
// 'new(s: string) => A & B'.
|
|
if (!mixinFlags[i]) {
|
|
let signatures = getSignaturesOfType(t, SignatureKind.Construct);
|
|
if (signatures.length && mixinCount > 0) {
|
|
signatures = map(signatures, s => {
|
|
const clone = cloneSignature(s);
|
|
clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i);
|
|
return clone;
|
|
});
|
|
}
|
|
constructSignatures = appendSignatures(constructSignatures, signatures);
|
|
}
|
|
callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call));
|
|
stringIndexInfo = intersectIndexInfos(stringIndexInfo, getIndexInfoOfType(t, IndexKind.String));
|
|
numberIndexInfo = intersectIndexInfos(numberIndexInfo, getIndexInfoOfType(t, IndexKind.Number));
|
|
}
|
|
setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, stringIndexInfo, numberIndexInfo);
|
|
}
|
|
|
|
function appendSignatures(signatures: Signature[] | undefined, newSignatures: readonly Signature[]) {
|
|
for (const sig of newSignatures) {
|
|
if (!signatures || every(signatures, s => !compareSignaturesIdentical(s, sig, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, compareTypesIdentical))) {
|
|
signatures = append(signatures, sig);
|
|
}
|
|
}
|
|
return signatures;
|
|
}
|
|
|
|
/**
|
|
* Converts an AnonymousType to a ResolvedType.
|
|
*/
|
|
function resolveAnonymousTypeMembers(type: AnonymousType) {
|
|
const symbol = getMergedSymbol(type.symbol);
|
|
if (type.target) {
|
|
setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false);
|
|
const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper!);
|
|
const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper!);
|
|
const stringIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.String), type.mapper!);
|
|
const numberIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.Number), type.mapper!);
|
|
setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
|
|
}
|
|
else if (symbol.flags & SymbolFlags.TypeLiteral) {
|
|
setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
const members = getMembersOfSymbol(symbol);
|
|
const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call));
|
|
const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New));
|
|
const stringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String);
|
|
const numberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number);
|
|
setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
|
|
}
|
|
else {
|
|
// Combinations of function, class, enum and module
|
|
let members = emptySymbols;
|
|
let stringIndexInfo: IndexInfo | undefined;
|
|
if (symbol.exports) {
|
|
members = getExportsOfSymbol(symbol);
|
|
if (symbol === globalThisSymbol) {
|
|
const varsOnly = createMap<Symbol>() as SymbolTable;
|
|
members.forEach(p => {
|
|
if (!(p.flags & SymbolFlags.BlockScoped)) {
|
|
varsOnly.set(p.escapedName, p);
|
|
}
|
|
});
|
|
members = varsOnly;
|
|
}
|
|
}
|
|
setStructuredTypeMembers(type, members, emptyArray, emptyArray, undefined, undefined);
|
|
if (symbol.flags & SymbolFlags.Class) {
|
|
const classType = getDeclaredTypeOfClassOrInterface(symbol);
|
|
const baseConstructorType = getBaseConstructorTypeOfClass(classType);
|
|
if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) {
|
|
members = createSymbolTable(getNamedMembers(members));
|
|
addInheritedMembers(members, getPropertiesOfType(baseConstructorType));
|
|
}
|
|
else if (baseConstructorType === anyType) {
|
|
stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
|
|
}
|
|
}
|
|
const numberIndexInfo = symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum ||
|
|
some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike))) ? enumNumberIndexInfo : undefined;
|
|
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
|
|
// We resolve the members before computing the signatures because a signature may use
|
|
// typeof with a qualified name expression that circularly references the type we are
|
|
// in the process of resolving (see issue #6072). The temporarily empty signature list
|
|
// will never be observed because a qualified name can't reference signatures.
|
|
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) {
|
|
type.callSignatures = getSignaturesOfSymbol(symbol);
|
|
}
|
|
// And likewise for construct signatures for classes
|
|
if (symbol.flags & SymbolFlags.Class) {
|
|
const classType = getDeclaredTypeOfClassOrInterface(symbol);
|
|
let constructSignatures = symbol.members ? getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)) : emptyArray;
|
|
if (symbol.flags & SymbolFlags.Function) {
|
|
constructSignatures = addRange(constructSignatures.slice(), mapDefined(
|
|
type.callSignatures,
|
|
sig => isJSConstructor(sig.declaration) ?
|
|
createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) :
|
|
undefined));
|
|
}
|
|
if (!constructSignatures.length) {
|
|
constructSignatures = getDefaultConstructSignatures(classType);
|
|
}
|
|
type.constructSignatures = constructSignatures;
|
|
}
|
|
}
|
|
}
|
|
|
|
function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
|
|
const indexInfo = getIndexInfoOfType(type.source, IndexKind.String);
|
|
const modifiers = getMappedTypeModifiers(type.mappedType);
|
|
const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true;
|
|
const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional;
|
|
const stringIndexInfo = indexInfo && createIndexInfo(inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly);
|
|
const members = createSymbolTable();
|
|
for (const prop of getPropertiesOfType(type.source)) {
|
|
const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0);
|
|
const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol;
|
|
inferredProp.declarations = prop.declarations;
|
|
inferredProp.nameType = getSymbolLinks(prop).nameType;
|
|
inferredProp.propertyType = getTypeOfSymbol(prop);
|
|
inferredProp.mappedType = type.mappedType;
|
|
inferredProp.constraintType = type.constraintType;
|
|
members.set(prop.escapedName, inferredProp);
|
|
}
|
|
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
|
|
}
|
|
|
|
// Return the lower bound of the key type in a mapped type. Intuitively, the lower
|
|
// bound includes those keys that are known to always be present, for example because
|
|
// because of constraints on type parameters (e.g. 'keyof T' for a constrained T).
|
|
function getLowerBoundOfKeyType(type: Type): Type {
|
|
if (type.flags & (TypeFlags.Any | TypeFlags.Primitive)) {
|
|
return type;
|
|
}
|
|
if (type.flags & TypeFlags.Index) {
|
|
return getIndexType(getApparentType((<IndexType>type).type));
|
|
}
|
|
if (type.flags & TypeFlags.Conditional) {
|
|
if ((<ConditionalType>type).root.isDistributive) {
|
|
const checkType = (<ConditionalType>type).checkType;
|
|
const constraint = getLowerBoundOfKeyType(checkType);
|
|
if (constraint !== checkType) {
|
|
return getConditionalTypeInstantiation(<ConditionalType>type, prependTypeMapping((<ConditionalType>type).root.checkType, constraint, (<ConditionalType>type).mapper));
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
if (type.flags & TypeFlags.Union) {
|
|
return getUnionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
|
|
}
|
|
if (type.flags & TypeFlags.Intersection) {
|
|
return getIntersectionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
|
|
}
|
|
return neverType;
|
|
}
|
|
|
|
/** Resolve the members of a mapped type { [P in K]: T } */
|
|
function resolveMappedTypeMembers(type: MappedType) {
|
|
const members: SymbolTable = createSymbolTable();
|
|
let stringIndexInfo: IndexInfo | undefined;
|
|
let numberIndexInfo: IndexInfo | undefined;
|
|
// Resolve upfront such that recursive references see an empty object type.
|
|
setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
// In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type,
|
|
// and T as the template type.
|
|
const typeParameter = getTypeParameterFromMappedType(type);
|
|
const constraintType = getConstraintTypeFromMappedType(type);
|
|
const templateType = getTemplateTypeFromMappedType(<MappedType>type.target || type);
|
|
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
|
|
const templateModifiers = getMappedTypeModifiers(type);
|
|
const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique;
|
|
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
|
|
// We have a { [P in keyof T]: X }
|
|
for (const prop of getPropertiesOfType(modifiersType)) {
|
|
addMemberForKeyType(getLiteralTypeFromProperty(prop, include));
|
|
}
|
|
if (modifiersType.flags & TypeFlags.Any || getIndexInfoOfType(modifiersType, IndexKind.String)) {
|
|
addMemberForKeyType(stringType);
|
|
}
|
|
if (!keyofStringsOnly && getIndexInfoOfType(modifiersType, IndexKind.Number)) {
|
|
addMemberForKeyType(numberType);
|
|
}
|
|
}
|
|
else {
|
|
forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
|
|
}
|
|
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
|
|
|
|
function addMemberForKeyType(t: Type) {
|
|
// Create a mapper from T to the current iteration type constituent. Then, if the
|
|
// mapped type is itself an instantiated type, combine the iteration mapper with the
|
|
// instantiation mapper.
|
|
const templateMapper = appendTypeMapping(type.mapper, typeParameter, t);
|
|
// If the current iteration type constituent is a string literal type, create a property.
|
|
// Otherwise, for type string create a string index signature.
|
|
if (isTypeUsableAsPropertyName(t)) {
|
|
const propName = getPropertyNameFromType(t);
|
|
const modifiersProp = getPropertyOfType(modifiersType, propName);
|
|
const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional ||
|
|
!(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional);
|
|
const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly ||
|
|
!(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp));
|
|
const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional;
|
|
const prop = <MappedSymbol>createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName,
|
|
CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0));
|
|
prop.mappedType = type;
|
|
prop.mapper = templateMapper;
|
|
if (modifiersProp) {
|
|
prop.syntheticOrigin = modifiersProp;
|
|
prop.declarations = modifiersProp.declarations;
|
|
}
|
|
prop.nameType = t;
|
|
members.set(propName, prop);
|
|
}
|
|
else if (t.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.Enum)) {
|
|
const propType = instantiateType(templateType, templateMapper);
|
|
if (t.flags & (TypeFlags.Any | TypeFlags.String)) {
|
|
stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
|
|
}
|
|
else {
|
|
numberIndexInfo = createIndexInfo(numberIndexInfo ? getUnionType([numberIndexInfo.type, propType]) : propType,
|
|
!!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getTypeOfMappedSymbol(symbol: MappedSymbol) {
|
|
if (!symbol.type) {
|
|
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
|
|
return errorType;
|
|
}
|
|
const templateType = getTemplateTypeFromMappedType(<MappedType>symbol.mappedType.target || symbol.mappedType);
|
|
const propType = instantiateType(templateType, symbol.mapper);
|
|
// When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the
|
|
// type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks
|
|
// mode, if the underlying property is optional we remove 'undefined' from the type.
|
|
let type = strictNullChecks && symbol.flags & SymbolFlags.Optional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType) :
|
|
symbol.checkFlags & CheckFlags.StripOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) :
|
|
propType;
|
|
if (!popTypeResolution()) {
|
|
error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(symbol.mappedType));
|
|
type = errorType;
|
|
}
|
|
symbol.type = type;
|
|
symbol.mapper = undefined!;
|
|
}
|
|
return symbol.type;
|
|
}
|
|
|
|
function getTypeParameterFromMappedType(type: MappedType) {
|
|
return type.typeParameter ||
|
|
(type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter)));
|
|
}
|
|
|
|
function getConstraintTypeFromMappedType(type: MappedType) {
|
|
return type.constraintType ||
|
|
(type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType);
|
|
}
|
|
|
|
function getTemplateTypeFromMappedType(type: MappedType) {
|
|
return type.templateType ||
|
|
(type.templateType = type.declaration.type ?
|
|
instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper) :
|
|
errorType);
|
|
}
|
|
|
|
function getConstraintDeclarationForMappedType(type: MappedType) {
|
|
return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter);
|
|
}
|
|
|
|
function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) {
|
|
const constraintDeclaration = getConstraintDeclarationForMappedType(type)!; // TODO: GH#18217
|
|
return constraintDeclaration.kind === SyntaxKind.TypeOperator &&
|
|
(<TypeOperatorNode>constraintDeclaration).operator === SyntaxKind.KeyOfKeyword;
|
|
}
|
|
|
|
function getModifiersTypeFromMappedType(type: MappedType) {
|
|
if (!type.modifiersType) {
|
|
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
|
|
// If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check
|
|
// AST nodes here because, when T is a non-generic type, the logic below eagerly resolves
|
|
// 'keyof T' to a literal union type and we can't recover T from that type.
|
|
type.modifiersType = instantiateType(getTypeFromTypeNode((<TypeOperatorNode>getConstraintDeclarationForMappedType(type)).type), type.mapper);
|
|
}
|
|
else {
|
|
// Otherwise, get the declared constraint type, and if the constraint type is a type parameter,
|
|
// get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T',
|
|
// the modifiers type is T. Otherwise, the modifiers type is unknown.
|
|
const declaredType = <MappedType>getTypeFromMappedTypeNode(type.declaration);
|
|
const constraint = getConstraintTypeFromMappedType(declaredType);
|
|
const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>constraint) : constraint;
|
|
type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((<IndexType>extendedConstraint).type, type.mapper) : unknownType;
|
|
}
|
|
}
|
|
return type.modifiersType;
|
|
}
|
|
|
|
function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers {
|
|
const declaration = type.declaration;
|
|
return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) |
|
|
(declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0);
|
|
}
|
|
|
|
function getMappedTypeOptionality(type: MappedType): number {
|
|
const modifiers = getMappedTypeModifiers(type);
|
|
return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0;
|
|
}
|
|
|
|
function getCombinedMappedTypeOptionality(type: MappedType): number {
|
|
const optionality = getMappedTypeOptionality(type);
|
|
const modifiersType = getModifiersTypeFromMappedType(type);
|
|
return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0);
|
|
}
|
|
|
|
function isPartialMappedType(type: Type) {
|
|
return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(<MappedType>type) & MappedTypeModifiers.IncludeOptional);
|
|
}
|
|
|
|
function isGenericMappedType(type: Type): type is MappedType {
|
|
return !!(getObjectFlags(type) & ObjectFlags.Mapped) && isGenericIndexType(getConstraintTypeFromMappedType(<MappedType>type));
|
|
}
|
|
|
|
function resolveStructuredTypeMembers(type: StructuredType): ResolvedType {
|
|
if (!(<ResolvedType>type).members) {
|
|
if (type.flags & TypeFlags.Object) {
|
|
if ((<ObjectType>type).objectFlags & ObjectFlags.Reference) {
|
|
resolveTypeReferenceMembers(<TypeReference>type);
|
|
}
|
|
else if ((<ObjectType>type).objectFlags & ObjectFlags.ClassOrInterface) {
|
|
resolveClassOrInterfaceMembers(<InterfaceType>type);
|
|
}
|
|
else if ((<ReverseMappedType>type).objectFlags & ObjectFlags.ReverseMapped) {
|
|
resolveReverseMappedTypeMembers(type as ReverseMappedType);
|
|
}
|
|
else if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
|
|
resolveAnonymousTypeMembers(<AnonymousType>type);
|
|
}
|
|
else if ((<MappedType>type).objectFlags & ObjectFlags.Mapped) {
|
|
resolveMappedTypeMembers(<MappedType>type);
|
|
}
|
|
}
|
|
else if (type.flags & TypeFlags.Union) {
|
|
resolveUnionTypeMembers(<UnionType>type);
|
|
}
|
|
else if (type.flags & TypeFlags.Intersection) {
|
|
resolveIntersectionTypeMembers(<IntersectionType>type);
|
|
}
|
|
}
|
|
return <ResolvedType>type;
|
|
}
|
|
|
|
/** Return properties of an object type or an empty array for other types */
|
|
function getPropertiesOfObjectType(type: Type): Symbol[] {
|
|
if (type.flags & TypeFlags.Object) {
|
|
return resolveStructuredTypeMembers(<ObjectType>type).properties;
|
|
}
|
|
return emptyArray;
|
|
}
|
|
|
|
/** If the given type is an object type and that type has a property by the given name,
|
|
* return the symbol for that property. Otherwise return undefined.
|
|
*/
|
|
function getPropertyOfObjectType(type: Type, name: __String): Symbol | undefined {
|
|
if (type.flags & TypeFlags.Object) {
|
|
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
|
|
const symbol = resolved.members.get(name);
|
|
if (symbol && symbolIsValue(symbol)) {
|
|
return symbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] {
|
|
if (!type.resolvedProperties) {
|
|
const members = createSymbolTable();
|
|
for (const current of type.types) {
|
|
for (const prop of getPropertiesOfType(current)) {
|
|
if (!members.has(prop.escapedName)) {
|
|
const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName);
|
|
if (combinedProp) {
|
|
members.set(prop.escapedName, combinedProp);
|
|
}
|
|
}
|
|
}
|
|
// The properties of a union type are those that are present in all constituent types, so
|
|
// we only need to check the properties of the first type
|
|
if (type.flags & TypeFlags.Union) {
|
|
break;
|
|
}
|
|
}
|
|
type.resolvedProperties = getNamedMembers(members);
|
|
}
|
|
return type.resolvedProperties;
|
|
}
|
|
|
|
function getPropertiesOfType(type: Type): Symbol[] {
|
|
type = getApparentType(getReducedType(type));
|
|
return type.flags & TypeFlags.UnionOrIntersection ?
|
|
getPropertiesOfUnionOrIntersectionType(<UnionType>type) :
|
|
getPropertiesOfObjectType(type);
|
|
}
|
|
|
|
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
|
|
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
|
|
return list.some(property => {
|
|
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
|
|
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
|
|
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
|
|
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
|
|
});
|
|
}
|
|
|
|
function getAllPossiblePropertiesOfTypes(types: readonly Type[]): Symbol[] {
|
|
const unionType = getUnionType(types);
|
|
if (!(unionType.flags & TypeFlags.Union)) {
|
|
return getAugmentedPropertiesOfType(unionType);
|
|
}
|
|
|
|
const props = createSymbolTable();
|
|
for (const memberType of types) {
|
|
for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) {
|
|
if (!props.has(escapedName)) {
|
|
const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName);
|
|
// May be undefined if the property is private
|
|
if (prop) props.set(escapedName, prop);
|
|
}
|
|
}
|
|
}
|
|
return arrayFrom(props.values());
|
|
}
|
|
|
|
function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): Type | undefined {
|
|
return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>type) :
|
|
type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(<IndexedAccessType>type) :
|
|
type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType(<ConditionalType>type) :
|
|
getBaseConstraintOfType(type);
|
|
}
|
|
|
|
function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type | undefined {
|
|
return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined;
|
|
}
|
|
|
|
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
|
|
return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined;
|
|
}
|
|
|
|
function getSimplifiedTypeOrConstraint(type: Type) {
|
|
const simplified = getSimplifiedType(type, /*writing*/ false);
|
|
return simplified !== type ? simplified : getConstraintOfType(type);
|
|
}
|
|
|
|
function getConstraintFromIndexedAccess(type: IndexedAccessType) {
|
|
const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType);
|
|
if (indexConstraint && indexConstraint !== type.indexType) {
|
|
const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint);
|
|
if (indexedAccess) {
|
|
return indexedAccess;
|
|
}
|
|
}
|
|
const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType);
|
|
if (objectConstraint && objectConstraint !== type.objectType) {
|
|
return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getDefaultConstraintOfConditionalType(type: ConditionalType) {
|
|
if (!type.resolvedDefaultConstraint) {
|
|
// An `any` branch of a conditional type would normally be viral - specifically, without special handling here,
|
|
// a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to
|
|
// just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type,
|
|
// in effect treating `any` like `never` rather than `unknown` in this location.
|
|
const trueConstraint = getInferredTrueTypeFromConditionalType(type);
|
|
const falseConstraint = getFalseTypeFromConditionalType(type);
|
|
type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]);
|
|
}
|
|
return type.resolvedDefaultConstraint;
|
|
}
|
|
|
|
function getConstraintOfDistributiveConditionalType(type: ConditionalType): Type | undefined {
|
|
// Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained
|
|
// type parameter. If so, create an instantiation of the conditional type where T is replaced
|
|
// with its constraint. We do this because if the constraint is a union type it will be distributed
|
|
// over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T'
|
|
// removes 'undefined' from T.
|
|
// We skip returning a distributive constraint for a restrictive instantiation of a conditional type
|
|
// as the constraint for all type params (check type included) have been replace with `unknown`, which
|
|
// is going to produce even more false positive/negative results than the distribute constraint already does.
|
|
// Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter
|
|
// a union - once negated types exist and are applied to the conditional false branch, this "constraint"
|
|
// likely doesn't need to exist.
|
|
if (type.root.isDistributive && type.restrictiveInstantiation !== type) {
|
|
const simplified = getSimplifiedType(type.checkType, /*writing*/ false);
|
|
const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified;
|
|
if (constraint && constraint !== type.checkType) {
|
|
const instantiated = getConditionalTypeInstantiation(type, prependTypeMapping(type.root.checkType, constraint, type.mapper));
|
|
if (!(instantiated.flags & TypeFlags.Never)) {
|
|
return instantiated;
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getConstraintFromConditionalType(type: ConditionalType) {
|
|
return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type);
|
|
}
|
|
|
|
function getConstraintOfConditionalType(type: ConditionalType) {
|
|
return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined;
|
|
}
|
|
|
|
function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) {
|
|
let constraints: Type[] | undefined;
|
|
let hasDisjointDomainType = false;
|
|
for (const t of types) {
|
|
if (t.flags & TypeFlags.Instantiable) {
|
|
// We keep following constraints as long as we have an instantiable type that is known
|
|
// not to be circular or infinite (hence we stop on index access types).
|
|
let constraint = getConstraintOfType(t);
|
|
while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) {
|
|
constraint = getConstraintOfType(constraint);
|
|
}
|
|
if (constraint) {
|
|
constraints = append(constraints, constraint);
|
|
if (targetIsUnion) {
|
|
constraints = append(constraints, t);
|
|
}
|
|
}
|
|
}
|
|
else if (t.flags & TypeFlags.DisjointDomains) {
|
|
hasDisjointDomainType = true;
|
|
}
|
|
}
|
|
// If the target is a union type or if we are intersecting with types belonging to one of the
|
|
// disjoint domains, we may end up producing a constraint that hasn't been examined before.
|
|
if (constraints && (targetIsUnion || hasDisjointDomainType)) {
|
|
if (hasDisjointDomainType) {
|
|
// We add any types belong to one of the disjoint domains because they might cause the final
|
|
// intersection operation to reduce the union constraints.
|
|
for (const t of types) {
|
|
if (t.flags & TypeFlags.DisjointDomains) {
|
|
constraints = append(constraints, t);
|
|
}
|
|
}
|
|
}
|
|
return getIntersectionType(constraints);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getBaseConstraintOfType(type: Type): Type | undefined {
|
|
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection)) {
|
|
const constraint = getResolvedBaseConstraint(<InstantiableType | UnionOrIntersectionType>type);
|
|
return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined;
|
|
}
|
|
return type.flags & TypeFlags.Index ? keyofConstraintType : undefined;
|
|
}
|
|
|
|
/**
|
|
* This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined`
|
|
* It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable)
|
|
*/
|
|
function getBaseConstraintOrType(type: Type) {
|
|
return getBaseConstraintOfType(type) || type;
|
|
}
|
|
|
|
function hasNonCircularBaseConstraint(type: InstantiableType): boolean {
|
|
return getResolvedBaseConstraint(type) !== circularConstraintType;
|
|
}
|
|
|
|
/**
|
|
* Return the resolved base constraint of a type variable. The noConstraintType singleton is returned if the
|
|
* type variable has no constraint, and the circularConstraintType singleton is returned if the constraint
|
|
* circularly references the type variable.
|
|
*/
|
|
function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type {
|
|
let nonTerminating = false;
|
|
return type.resolvedBaseConstraint ||
|
|
(type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type));
|
|
|
|
function getImmediateBaseConstraint(t: Type): Type {
|
|
if (!t.immediateBaseConstraint) {
|
|
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
|
|
return circularConstraintType;
|
|
}
|
|
if (constraintDepth >= 50) {
|
|
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
|
|
// very high likelihood we're dealing with an infinite generic type that perpetually generates
|
|
// new type identities as we descend into it. We stop the recursion here and mark this type
|
|
// and the outer types as having circular constraints.
|
|
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
|
|
nonTerminating = true;
|
|
return t.immediateBaseConstraint = noConstraintType;
|
|
}
|
|
constraintDepth++;
|
|
let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
|
|
constraintDepth--;
|
|
if (!popTypeResolution()) {
|
|
if (t.flags & TypeFlags.TypeParameter) {
|
|
const errorNode = getConstraintDeclaration(<TypeParameter>t);
|
|
if (errorNode) {
|
|
const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t));
|
|
if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) {
|
|
addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location));
|
|
}
|
|
}
|
|
}
|
|
result = circularConstraintType;
|
|
}
|
|
if (nonTerminating) {
|
|
result = circularConstraintType;
|
|
}
|
|
t.immediateBaseConstraint = result || noConstraintType;
|
|
}
|
|
return t.immediateBaseConstraint;
|
|
}
|
|
|
|
function getBaseConstraint(t: Type): Type | undefined {
|
|
const c = getImmediateBaseConstraint(t);
|
|
return c !== noConstraintType && c !== circularConstraintType ? c : undefined;
|
|
}
|
|
|
|
function computeBaseConstraint(t: Type): Type | undefined {
|
|
if (t.flags & TypeFlags.TypeParameter) {
|
|
const constraint = getConstraintFromTypeParameter(<TypeParameter>t);
|
|
return (t as TypeParameter).isThisType || !constraint ?
|
|
constraint :
|
|
getBaseConstraint(constraint);
|
|
}
|
|
if (t.flags & TypeFlags.UnionOrIntersection) {
|
|
const types = (<UnionOrIntersectionType>t).types;
|
|
const baseTypes: Type[] = [];
|
|
for (const type of types) {
|
|
const baseType = getBaseConstraint(type);
|
|
if (baseType) {
|
|
baseTypes.push(baseType);
|
|
}
|
|
}
|
|
return t.flags & TypeFlags.Union && baseTypes.length === types.length ? getUnionType(baseTypes) :
|
|
t.flags & TypeFlags.Intersection && baseTypes.length ? getIntersectionType(baseTypes) :
|
|
undefined;
|
|
}
|
|
if (t.flags & TypeFlags.Index) {
|
|
return keyofConstraintType;
|
|
}
|
|
if (t.flags & TypeFlags.IndexedAccess) {
|
|
const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
|
|
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
|
|
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType);
|
|
return baseIndexedAccess && getBaseConstraint(baseIndexedAccess);
|
|
}
|
|
if (t.flags & TypeFlags.Conditional) {
|
|
const constraint = getConstraintFromConditionalType(<ConditionalType>t);
|
|
constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
|
|
const result = constraint && getBaseConstraint(constraint);
|
|
constraintDepth--;
|
|
return result;
|
|
}
|
|
if (t.flags & TypeFlags.Substitution) {
|
|
return getBaseConstraint((<SubstitutionType>t).substitute);
|
|
}
|
|
return t;
|
|
}
|
|
}
|
|
|
|
function getApparentTypeOfIntersectionType(type: IntersectionType) {
|
|
return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type, /*apparentType*/ true));
|
|
}
|
|
|
|
function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined {
|
|
if (!typeParameter.default) {
|
|
if (typeParameter.target) {
|
|
const targetDefault = getResolvedTypeParameterDefault(typeParameter.target);
|
|
typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType;
|
|
}
|
|
else {
|
|
// To block recursion, set the initial value to the resolvingDefaultType.
|
|
typeParameter.default = resolvingDefaultType;
|
|
const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default);
|
|
const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType;
|
|
if (typeParameter.default === resolvingDefaultType) {
|
|
// If we have not been called recursively, set the correct default type.
|
|
typeParameter.default = defaultType;
|
|
}
|
|
}
|
|
}
|
|
else if (typeParameter.default === resolvingDefaultType) {
|
|
// If we are called recursively for this type parameter, mark the default as circular.
|
|
typeParameter.default = circularConstraintType;
|
|
}
|
|
return typeParameter.default;
|
|
}
|
|
|
|
/**
|
|
* Gets the default type for a type parameter.
|
|
*
|
|
* If the type parameter is the result of an instantiation, this gets the instantiated
|
|
* default type of its target. If the type parameter has no default type or the default is
|
|
* circular, `undefined` is returned.
|
|
*/
|
|
function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined {
|
|
const defaultType = getResolvedTypeParameterDefault(typeParameter);
|
|
return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined;
|
|
}
|
|
|
|
function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) {
|
|
return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether the declaration of a typeParameter has a default type.
|
|
*/
|
|
function hasTypeParameterDefault(typeParameter: TypeParameter): boolean {
|
|
return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default));
|
|
}
|
|
|
|
function getApparentTypeOfMappedType(type: MappedType) {
|
|
return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type));
|
|
}
|
|
|
|
function getResolvedApparentTypeOfMappedType(type: MappedType) {
|
|
const typeVariable = getHomomorphicTypeVariable(type);
|
|
if (typeVariable) {
|
|
const constraint = getConstraintOfTypeParameter(typeVariable);
|
|
if (constraint && (isArrayType(constraint) || isTupleType(constraint))) {
|
|
return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper));
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* For a type parameter, return the base constraint of the type parameter. For the string, number,
|
|
* boolean, and symbol primitive types, return the corresponding object types. Otherwise return the
|
|
* type itself.
|
|
*/
|
|
function getApparentType(type: Type): Type {
|
|
const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || unknownType : type;
|
|
return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(<MappedType>t) :
|
|
t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
|
|
t.flags & TypeFlags.StringLike ? globalStringType :
|
|
t.flags & TypeFlags.NumberLike ? globalNumberType :
|
|
t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType(/*reportErrors*/ languageVersion >= ScriptTarget.ESNext) :
|
|
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
|
|
t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) :
|
|
t.flags & TypeFlags.NonPrimitive ? emptyObjectType :
|
|
t.flags & TypeFlags.Index ? keyofConstraintType :
|
|
t.flags & TypeFlags.Unknown && !strictNullChecks ? emptyObjectType :
|
|
t;
|
|
}
|
|
|
|
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined {
|
|
const propSet = createMap<Symbol>();
|
|
let indexTypes: Type[] | undefined;
|
|
const isUnion = containingType.flags & TypeFlags.Union;
|
|
const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0;
|
|
// Flags we want to propagate to the result if they exist in all source symbols
|
|
let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional;
|
|
let syntheticFlag = CheckFlags.SyntheticMethod;
|
|
let checkFlags = 0;
|
|
for (const current of containingType.types) {
|
|
const type = getApparentType(current);
|
|
if (!(type === errorType || type.flags & TypeFlags.Never)) {
|
|
const prop = getPropertyOfType(type, name);
|
|
const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0;
|
|
if (prop && !(modifiers & excludeModifiers)) {
|
|
if (isUnion) {
|
|
optionalFlag |= (prop.flags & SymbolFlags.Optional);
|
|
}
|
|
else {
|
|
optionalFlag &= prop.flags;
|
|
}
|
|
const id = "" + getSymbolId(prop);
|
|
if (!propSet.has(id)) {
|
|
propSet.set(id, prop);
|
|
}
|
|
checkFlags |= (isReadonlySymbol(prop) ? CheckFlags.Readonly : 0) |
|
|
(!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) |
|
|
(modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) |
|
|
(modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) |
|
|
(modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0);
|
|
if (!isPrototypeProperty(prop)) {
|
|
syntheticFlag = CheckFlags.SyntheticProperty;
|
|
}
|
|
}
|
|
else if (isUnion) {
|
|
const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String));
|
|
if (indexInfo) {
|
|
checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0);
|
|
indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type);
|
|
}
|
|
else if (isObjectLiteralType(type)) {
|
|
checkFlags |= CheckFlags.WritePartial;
|
|
indexTypes = append(indexTypes, undefinedType);
|
|
}
|
|
else {
|
|
checkFlags |= CheckFlags.ReadPartial;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!propSet.size) {
|
|
return undefined;
|
|
}
|
|
const props = arrayFrom(propSet.values());
|
|
if (props.length === 1 && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) {
|
|
return props[0];
|
|
}
|
|
let declarations: Declaration[] | undefined;
|
|
let firstType: Type | undefined;
|
|
let nameType: Type | undefined;
|
|
const propTypes: Type[] = [];
|
|
let firstValueDeclaration: Declaration | undefined;
|
|
let hasNonUniformValueDeclaration = false;
|
|
for (const prop of props) {
|
|
if (!firstValueDeclaration) {
|
|
firstValueDeclaration = prop.valueDeclaration;
|
|
}
|
|
else if (prop.valueDeclaration !== firstValueDeclaration) {
|
|
hasNonUniformValueDeclaration = true;
|
|
}
|
|
declarations = addRange(declarations, prop.declarations);
|
|
const type = getTypeOfSymbol(prop);
|
|
if (!firstType) {
|
|
firstType = type;
|
|
nameType = getSymbolLinks(prop).nameType;
|
|
}
|
|
else if (type !== firstType) {
|
|
checkFlags |= CheckFlags.HasNonUniformType;
|
|
}
|
|
if (isLiteralType(type)) {
|
|
checkFlags |= CheckFlags.HasLiteralType;
|
|
}
|
|
if (type.flags & TypeFlags.Never) {
|
|
checkFlags |= CheckFlags.HasNeverType;
|
|
}
|
|
propTypes.push(type);
|
|
}
|
|
addRange(propTypes, indexTypes);
|
|
const result = createSymbol(SymbolFlags.Property | optionalFlag, name, syntheticFlag | checkFlags);
|
|
result.containingType = containingType;
|
|
if (!hasNonUniformValueDeclaration && firstValueDeclaration) {
|
|
result.valueDeclaration = firstValueDeclaration;
|
|
|
|
// Inherit information about parent type.
|
|
if (firstValueDeclaration.symbol.parent) {
|
|
result.parent = firstValueDeclaration.symbol.parent;
|
|
}
|
|
}
|
|
|
|
result.declarations = declarations!;
|
|
result.nameType = nameType;
|
|
if (propTypes.length > 2) {
|
|
// When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed
|
|
result.checkFlags |= CheckFlags.DeferredType;
|
|
result.deferralParent = containingType;
|
|
result.deferralConstituents = propTypes;
|
|
}
|
|
else {
|
|
result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Return the symbol for a given property in a union or intersection type, or undefined if the property
|
|
// does not exist in any constituent type. Note that the returned property may only be present in some
|
|
// constituents, in which case the isPartial flag is set when the containing type is union type. We need
|
|
// these partial properties when identifying discriminant properties, but otherwise they are filtered out
|
|
// and do not appear to be present in the union type.
|
|
function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String): Symbol | undefined {
|
|
const properties = type.propertyCache || (type.propertyCache = createSymbolTable());
|
|
let property = properties.get(name);
|
|
if (!property) {
|
|
property = createUnionOrIntersectionProperty(type, name);
|
|
if (property) {
|
|
properties.set(name, property);
|
|
}
|
|
}
|
|
return property;
|
|
}
|
|
|
|
function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String): Symbol | undefined {
|
|
const property = getUnionOrIntersectionProperty(type, name);
|
|
// We need to filter out partial properties in union types
|
|
return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined;
|
|
}
|
|
|
|
/**
|
|
* Return the reduced form of the given type. For a union type, it is a union of the normalized constituent types.
|
|
* For an intersection of types containing one or more mututally exclusive discriminant properties, it is 'never'.
|
|
* For all other types, it is simply the type itself. Discriminant properties are considered mutually exclusive when
|
|
* no constituent property has type 'never', but the intersection of the constituent property types is 'never'.
|
|
*/
|
|
function getReducedType(type: Type): Type {
|
|
if (type.flags & TypeFlags.Union && (<UnionType>type).objectFlags & ObjectFlags.ContainsIntersections) {
|
|
return (<UnionType>type).resolvedReducedType || ((<UnionType>type).resolvedReducedType = getReducedUnionType(<UnionType>type));
|
|
}
|
|
else if (type.flags & TypeFlags.Intersection) {
|
|
if (!((<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) {
|
|
(<IntersectionType>type).objectFlags |= ObjectFlags.IsNeverIntersectionComputed |
|
|
(some(getPropertiesOfUnionOrIntersectionType(<IntersectionType>type), isDiscriminantWithNeverType) ? ObjectFlags.IsNeverIntersection : 0);
|
|
}
|
|
return (<IntersectionType>type).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getReducedUnionType(unionType: UnionType) {
|
|
const reducedTypes = sameMap(unionType.types, getReducedType);
|
|
if (reducedTypes === unionType.types) {
|
|
return unionType;
|
|
}
|
|
const reduced = getUnionType(reducedTypes);
|
|
if (reduced.flags & TypeFlags.Union) {
|
|
(<UnionType>reduced).resolvedReducedType = reduced;
|
|
}
|
|
return reduced;
|
|
}
|
|
|
|
function isDiscriminantWithNeverType(prop: Symbol) {
|
|
return !(prop.flags & SymbolFlags.Optional) &&
|
|
(getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant &&
|
|
!!(getTypeOfSymbol(prop).flags & TypeFlags.Never);
|
|
}
|
|
|
|
/**
|
|
* Return the symbol for the property with the given name in the given type. Creates synthetic union properties when
|
|
* necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from
|
|
* Object and Function as appropriate.
|
|
*
|
|
* @param type a type to look up property from
|
|
* @param name a name of property to look up in a given type
|
|
*/
|
|
function getPropertyOfType(type: Type, name: __String): Symbol | undefined {
|
|
type = getApparentType(getReducedType(type));
|
|
if (type.flags & TypeFlags.Object) {
|
|
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
|
|
const symbol = resolved.members.get(name);
|
|
if (symbol && symbolIsValue(symbol)) {
|
|
return symbol;
|
|
}
|
|
const functionType = resolved === anyFunctionType ? globalFunctionType :
|
|
resolved.callSignatures.length ? globalCallableFunctionType :
|
|
resolved.constructSignatures.length ? globalNewableFunctionType :
|
|
undefined;
|
|
if (functionType) {
|
|
const symbol = getPropertyOfObjectType(functionType, name);
|
|
if (symbol) {
|
|
return symbol;
|
|
}
|
|
}
|
|
return getPropertyOfObjectType(globalObjectType, name);
|
|
}
|
|
if (type.flags & TypeFlags.UnionOrIntersection) {
|
|
return getPropertyOfUnionOrIntersectionType(<UnionOrIntersectionType>type, name);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): readonly Signature[] {
|
|
if (type.flags & TypeFlags.StructuredType) {
|
|
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
|
|
return kind === SignatureKind.Call ? resolved.callSignatures : resolved.constructSignatures;
|
|
}
|
|
return emptyArray;
|
|
}
|
|
|
|
/**
|
|
* Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and
|
|
* maps primitive types and type parameters are to their apparent types.
|
|
*/
|
|
function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] {
|
|
return getSignaturesOfStructuredType(getApparentType(getReducedType(type)), kind);
|
|
}
|
|
|
|
function getIndexInfoOfStructuredType(type: Type, kind: IndexKind): IndexInfo | undefined {
|
|
if (type.flags & TypeFlags.StructuredType) {
|
|
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
|
|
return kind === IndexKind.String ? resolved.stringIndexInfo : resolved.numberIndexInfo;
|
|
}
|
|
}
|
|
|
|
function getIndexTypeOfStructuredType(type: Type, kind: IndexKind): Type | undefined {
|
|
const info = getIndexInfoOfStructuredType(type, kind);
|
|
return info && info.type;
|
|
}
|
|
|
|
// Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and
|
|
// maps primitive types and type parameters are to their apparent types.
|
|
function getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined {
|
|
return getIndexInfoOfStructuredType(getApparentType(getReducedType(type)), kind);
|
|
}
|
|
|
|
// Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and
|
|
// maps primitive types and type parameters are to their apparent types.
|
|
function getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined {
|
|
return getIndexTypeOfStructuredType(getApparentType(getReducedType(type)), kind);
|
|
}
|
|
|
|
function getImplicitIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined {
|
|
if (isObjectTypeWithInferableIndex(type)) {
|
|
const propTypes: Type[] = [];
|
|
for (const prop of getPropertiesOfType(type)) {
|
|
if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) {
|
|
propTypes.push(getTypeOfSymbol(prop));
|
|
}
|
|
}
|
|
if (kind === IndexKind.String) {
|
|
append(propTypes, getIndexTypeOfType(type, IndexKind.Number));
|
|
}
|
|
if (propTypes.length) {
|
|
return getUnionType(propTypes);
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual
|
|
// type checking functions).
|
|
function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] | undefined {
|
|
let result: TypeParameter[] | undefined;
|
|
for (const node of getEffectiveTypeParameterDeclarations(declaration)) {
|
|
result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function symbolsToArray(symbols: SymbolTable): Symbol[] {
|
|
const result: Symbol[] = [];
|
|
symbols.forEach((symbol, id) => {
|
|
if (!isReservedMemberName(id)) {
|
|
result.push(symbol);
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function isJSDocOptionalParameter(node: ParameterDeclaration) {
|
|
return isInJSFile(node) && (
|
|
// node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType
|
|
node.type && node.type.kind === SyntaxKind.JSDocOptionalType
|
|
|| getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) =>
|
|
isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType));
|
|
}
|
|
|
|
function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) {
|
|
if (isExternalModuleNameRelative(moduleName)) {
|
|
return undefined;
|
|
}
|
|
const symbol = getSymbol(globals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule);
|
|
// merged symbol is module declaration symbol combined with all augmentations
|
|
return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol;
|
|
}
|
|
|
|
function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag) {
|
|
if (hasQuestionToken(node) || isOptionalJSDocParameterTag(node) || isJSDocOptionalParameter(node)) {
|
|
return true;
|
|
}
|
|
|
|
if (node.initializer) {
|
|
const signature = getSignatureFromDeclaration(node.parent);
|
|
const parameterIndex = node.parent.parameters.indexOf(node);
|
|
Debug.assert(parameterIndex >= 0);
|
|
return parameterIndex >= getMinArgumentCount(signature, /*strongArityForUntypedJS*/ true);
|
|
}
|
|
const iife = getImmediatelyInvokedFunctionExpression(node.parent);
|
|
if (iife) {
|
|
return !node.type &&
|
|
!node.dotDotDotToken &&
|
|
node.parent.parameters.indexOf(node) >= iife.arguments.length;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isOptionalJSDocParameterTag(node: Node): node is JSDocParameterTag {
|
|
if (!isJSDocParameterTag(node)) {
|
|
return false;
|
|
}
|
|
const { isBracketed, typeExpression } = node;
|
|
return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType;
|
|
}
|
|
|
|
function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: Type | undefined): TypePredicate {
|
|
return { kind, parameterName, parameterIndex, type } as TypePredicate;
|
|
}
|
|
|
|
/**
|
|
* Gets the minimum number of type arguments needed to satisfy all non-optional type
|
|
* parameters.
|
|
*/
|
|
function getMinTypeArgumentCount(typeParameters: readonly TypeParameter[] | undefined): number {
|
|
let minTypeArgumentCount = 0;
|
|
if (typeParameters) {
|
|
for (let i = 0; i < typeParameters.length; i++) {
|
|
if (!hasTypeParameterDefault(typeParameters[i])) {
|
|
minTypeArgumentCount = i + 1;
|
|
}
|
|
}
|
|
}
|
|
return minTypeArgumentCount;
|
|
}
|
|
|
|
/**
|
|
* Fill in default types for unsupplied type arguments. If `typeArguments` is undefined
|
|
* when a default type is supplied, a new array will be created and returned.
|
|
*
|
|
* @param typeArguments The supplied type arguments.
|
|
* @param typeParameters The requested type parameters.
|
|
* @param minTypeArgumentCount The minimum number of required type arguments.
|
|
*/
|
|
function fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[];
|
|
function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined;
|
|
function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) {
|
|
const numTypeParameters = length(typeParameters);
|
|
if (!numTypeParameters) {
|
|
return [];
|
|
}
|
|
const numTypeArguments = length(typeArguments);
|
|
if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) {
|
|
const result = typeArguments ? typeArguments.slice() : [];
|
|
// Map invalid forward references in default types to the error type
|
|
for (let i = numTypeArguments; i < numTypeParameters; i++) {
|
|
result[i] = errorType;
|
|
}
|
|
const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
|
|
for (let i = numTypeArguments; i < numTypeParameters; i++) {
|
|
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
|
|
if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) {
|
|
defaultType = anyType;
|
|
}
|
|
result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType;
|
|
}
|
|
result.length = typeParameters!.length;
|
|
return result;
|
|
}
|
|
return typeArguments && typeArguments.slice();
|
|
}
|
|
|
|
function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature {
|
|
const links = getNodeLinks(declaration);
|
|
if (!links.resolvedSignature) {
|
|
const parameters: Symbol[] = [];
|
|
let flags = SignatureFlags.None;
|
|
let minArgumentCount = 0;
|
|
let thisParameter: Symbol | undefined;
|
|
let hasThisParameter = false;
|
|
const iife = getImmediatelyInvokedFunctionExpression(declaration);
|
|
const isJSConstructSignature = isJSDocConstructSignature(declaration);
|
|
const isUntypedSignatureInJSFile = !iife &&
|
|
isInJSFile(declaration) &&
|
|
isValueSignatureDeclaration(declaration) &&
|
|
!hasJSDocParameterTags(declaration) &&
|
|
!getJSDocType(declaration);
|
|
if (isUntypedSignatureInJSFile) {
|
|
flags |= SignatureFlags.IsUntypedSignatureInJSFile;
|
|
}
|
|
|
|
// If this is a JSDoc construct signature, then skip the first parameter in the
|
|
// parameter list. The first parameter represents the return type of the construct
|
|
// signature.
|
|
for (let i = isJSConstructSignature ? 1 : 0; i < declaration.parameters.length; i++) {
|
|
const param = declaration.parameters[i];
|
|
|
|
let paramSymbol = param.symbol;
|
|
const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type;
|
|
// Include parameter symbol instead of property symbol in the signature
|
|
if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) {
|
|
const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false);
|
|
paramSymbol = resolvedSymbol!;
|
|
}
|
|
if (i === 0 && paramSymbol.escapedName === InternalSymbolName.This) {
|
|
hasThisParameter = true;
|
|
thisParameter = param.symbol;
|
|
}
|
|
else {
|
|
parameters.push(paramSymbol);
|
|
}
|
|
|
|
if (type && type.kind === SyntaxKind.LiteralType) {
|
|
flags |= SignatureFlags.HasLiteralTypes;
|
|
}
|
|
|
|
// Record a new minimum argument count if this is not an optional parameter
|
|
const isOptionalParameter = isOptionalJSDocParameterTag(param) ||
|
|
param.initializer || param.questionToken || param.dotDotDotToken ||
|
|
iife && parameters.length > iife.arguments.length && !type ||
|
|
isJSDocOptionalParameter(param);
|
|
if (!isOptionalParameter) {
|
|
minArgumentCount = parameters.length;
|
|
}
|
|
}
|
|
|
|
// If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation
|
|
if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) &&
|
|
!hasNonBindableDynamicName(declaration) &&
|
|
(!hasThisParameter || !thisParameter)) {
|
|
const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor;
|
|
const other = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(declaration), otherKind);
|
|
if (other) {
|
|
thisParameter = getAnnotatedAccessorThisParameter(other);
|
|
}
|
|
}
|
|
|
|
const classType = declaration.kind === SyntaxKind.Constructor ?
|
|
getDeclaredTypeOfClassOrInterface(getMergedSymbol((<ClassDeclaration>declaration.parent).symbol))
|
|
: undefined;
|
|
const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration);
|
|
if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) {
|
|
flags |= SignatureFlags.HasRestParameter;
|
|
}
|
|
links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters,
|
|
/*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined,
|
|
minArgumentCount, flags);
|
|
}
|
|
return links.resolvedSignature;
|
|
}
|
|
|
|
/**
|
|
* A JS function gets a synthetic rest parameter if it references `arguments` AND:
|
|
* 1. It has no parameters but at least one `@param` with a type that starts with `...`
|
|
* OR
|
|
* 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...`
|
|
*/
|
|
function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean {
|
|
if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) {
|
|
return false;
|
|
}
|
|
const lastParam = lastOrUndefined(declaration.parameters);
|
|
const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag);
|
|
const lastParamVariadicType = firstDefined(lastParamTags, p =>
|
|
p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined);
|
|
|
|
const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String, CheckFlags.RestParameter);
|
|
syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : anyArrayType;
|
|
if (lastParamVariadicType) {
|
|
// Replace the last parameter with a rest parameter.
|
|
parameters.pop();
|
|
}
|
|
parameters.push(syntheticArgsSymbol);
|
|
return true;
|
|
}
|
|
|
|
function getSignatureOfTypeTag(node: SignatureDeclaration | JSDocSignature) {
|
|
const typeTag = isInJSFile(node) ? getJSDocTypeTag(node) : undefined;
|
|
const signature = typeTag && typeTag.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression));
|
|
return signature && getErasedSignature(signature);
|
|
}
|
|
|
|
function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) {
|
|
const signature = getSignatureOfTypeTag(node);
|
|
return signature && getReturnTypeOfSignature(signature);
|
|
}
|
|
|
|
function containsArgumentsReference(declaration: SignatureDeclaration): boolean {
|
|
const links = getNodeLinks(declaration);
|
|
if (links.containsArgumentsReference === undefined) {
|
|
if (links.flags & NodeCheckFlags.CaptureArguments) {
|
|
links.containsArgumentsReference = true;
|
|
}
|
|
else {
|
|
links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!);
|
|
}
|
|
}
|
|
return links.containsArgumentsReference;
|
|
|
|
function traverse(node: Node): boolean {
|
|
if (!node) return false;
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return (<Identifier>node).escapedText === "arguments" && isExpressionNode(node);
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
return (<NamedDeclaration>node).name!.kind === SyntaxKind.ComputedPropertyName
|
|
&& traverse((<NamedDeclaration>node).name!);
|
|
|
|
default:
|
|
return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] {
|
|
if (!symbol) return emptyArray;
|
|
const result: Signature[] = [];
|
|
for (let i = 0; i < symbol.declarations.length; i++) {
|
|
const decl = symbol.declarations[i];
|
|
if (!isFunctionLike(decl)) continue;
|
|
// Don't include signature if node is the implementation of an overloaded function. A node is considered
|
|
// an implementation node if it has a body and the previous node is of the same kind and immediately
|
|
// precedes the implementation node (i.e. has the same parent and ends where the implementation starts).
|
|
if (i > 0 && (decl as FunctionLikeDeclaration).body) {
|
|
const previous = symbol.declarations[i - 1];
|
|
if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) {
|
|
continue;
|
|
}
|
|
}
|
|
result.push(getSignatureFromDeclaration(decl));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function resolveExternalModuleTypeByLiteral(name: StringLiteral) {
|
|
const moduleSym = resolveExternalModuleName(name, name);
|
|
if (moduleSym) {
|
|
const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym);
|
|
if (resolvedModuleSymbol) {
|
|
return getTypeOfSymbol(resolvedModuleSymbol);
|
|
}
|
|
}
|
|
|
|
return anyType;
|
|
}
|
|
|
|
function getThisTypeOfSignature(signature: Signature): Type | undefined {
|
|
if (signature.thisParameter) {
|
|
return getTypeOfSymbol(signature.thisParameter);
|
|
}
|
|
}
|
|
|
|
function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined {
|
|
if (!signature.resolvedTypePredicate) {
|
|
if (signature.target) {
|
|
const targetTypePredicate = getTypePredicateOfSignature(signature.target);
|
|
signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper!) : noTypePredicate;
|
|
}
|
|
else if (signature.unionSignatures) {
|
|
signature.resolvedTypePredicate = getUnionTypePredicate(signature.unionSignatures) || noTypePredicate;
|
|
}
|
|
else {
|
|
const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration);
|
|
let jsdocPredicate: TypePredicate | undefined;
|
|
if (!type && isInJSFile(signature.declaration)) {
|
|
const jsdocSignature = getSignatureOfTypeTag(signature.declaration!);
|
|
if (jsdocSignature && signature !== jsdocSignature) {
|
|
jsdocPredicate = getTypePredicateOfSignature(jsdocSignature);
|
|
}
|
|
}
|
|
signature.resolvedTypePredicate = type && isTypePredicateNode(type) ?
|
|
createTypePredicateFromTypePredicateNode(type, signature) :
|
|
jsdocPredicate || noTypePredicate;
|
|
}
|
|
Debug.assert(!!signature.resolvedTypePredicate);
|
|
}
|
|
return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate;
|
|
}
|
|
|
|
function createTypePredicateFromTypePredicateNode(node: TypePredicateNode, signature: Signature): TypePredicate {
|
|
const parameterName = node.parameterName;
|
|
const type = node.type && getTypeFromTypeNode(node.type);
|
|
return parameterName.kind === SyntaxKind.ThisType ?
|
|
createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsThis : TypePredicateKind.This, /*parameterName*/ undefined, /*parameterIndex*/ undefined, type) :
|
|
createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsIdentifier : TypePredicateKind.Identifier, parameterName.escapedText as string,
|
|
findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type);
|
|
}
|
|
|
|
function getReturnTypeOfSignature(signature: Signature): Type {
|
|
if (!signature.resolvedReturnType) {
|
|
if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) {
|
|
return errorType;
|
|
}
|
|
let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) :
|
|
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) :
|
|
getReturnTypeFromAnnotation(signature.declaration!) ||
|
|
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
|
|
if (signature.flags & SignatureFlags.IsInnerCallChain) {
|
|
type = addOptionalTypeMarker(type);
|
|
}
|
|
else if (signature.flags & SignatureFlags.IsOuterCallChain) {
|
|
type = getOptionalType(type);
|
|
}
|
|
if (!popTypeResolution()) {
|
|
if (signature.declaration) {
|
|
const typeNode = getEffectiveReturnTypeNode(signature.declaration);
|
|
if (typeNode) {
|
|
error(typeNode, Diagnostics.Return_type_annotation_circularly_references_itself);
|
|
}
|
|
else if (noImplicitAny) {
|
|
const declaration = <Declaration>signature.declaration;
|
|
const name = getNameOfDeclaration(declaration);
|
|
if (name) {
|
|
error(name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, declarationNameToString(name));
|
|
}
|
|
else {
|
|
error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions);
|
|
}
|
|
}
|
|
}
|
|
type = anyType;
|
|
}
|
|
signature.resolvedReturnType = type;
|
|
}
|
|
return signature.resolvedReturnType;
|
|
}
|
|
|
|
function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) {
|
|
if (declaration.kind === SyntaxKind.Constructor) {
|
|
return getDeclaredTypeOfClassOrInterface(getMergedSymbol((<ClassDeclaration>declaration.parent).symbol));
|
|
}
|
|
if (isJSDocConstructSignature(declaration)) {
|
|
return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217
|
|
}
|
|
const typeNode = getEffectiveReturnTypeNode(declaration);
|
|
if (typeNode) {
|
|
return getTypeFromTypeNode(typeNode);
|
|
}
|
|
if (declaration.kind === SyntaxKind.GetAccessor && !hasNonBindableDynamicName(declaration)) {
|
|
const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration);
|
|
if (jsDocType) {
|
|
return jsDocType;
|
|
}
|
|
const setter = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(declaration), SyntaxKind.SetAccessor);
|
|
const setterType = getAnnotatedAccessorType(setter);
|
|
if (setterType) {
|
|
return setterType;
|
|
}
|
|
}
|
|
return getReturnTypeOfTypeTag(declaration);
|
|
}
|
|
|
|
function isResolvingReturnTypeOfSignature(signature: Signature) {
|
|
return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0;
|
|
}
|
|
|
|
function getRestTypeOfSignature(signature: Signature): Type {
|
|
return tryGetRestTypeOfSignature(signature) || anyType;
|
|
}
|
|
|
|
function tryGetRestTypeOfSignature(signature: Signature): Type | undefined {
|
|
if (signatureHasRestParameter(signature)) {
|
|
const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
|
|
const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType;
|
|
return restType && getIndexTypeOfType(restType, IndexKind.Number);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature {
|
|
const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript));
|
|
if (inferredTypeParameters) {
|
|
const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature));
|
|
if (returnSignature) {
|
|
const newReturnSignature = cloneSignature(returnSignature);
|
|
newReturnSignature.typeParameters = inferredTypeParameters;
|
|
const newInstantiatedSignature = cloneSignature(instantiatedSignature);
|
|
newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature);
|
|
return newInstantiatedSignature;
|
|
}
|
|
}
|
|
return instantiatedSignature;
|
|
}
|
|
|
|
function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: readonly Type[] | undefined): Signature {
|
|
const instantiations = signature.instantiations || (signature.instantiations = createMap<Signature>());
|
|
const id = getTypeListId(typeArguments);
|
|
let instantiation = instantiations.get(id);
|
|
if (!instantiation) {
|
|
instantiations.set(id, instantiation = createSignatureInstantiation(signature, typeArguments));
|
|
}
|
|
return instantiation;
|
|
}
|
|
|
|
function createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature {
|
|
return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true);
|
|
}
|
|
|
|
function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper {
|
|
return createTypeMapper(signature.typeParameters!, typeArguments);
|
|
}
|
|
|
|
function getErasedSignature(signature: Signature): Signature {
|
|
return signature.typeParameters ?
|
|
signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) :
|
|
signature;
|
|
}
|
|
|
|
function createErasedSignature(signature: Signature) {
|
|
// Create an instantiation of the signature where all type arguments are the any type.
|
|
return instantiateSignature(signature, createTypeEraser(signature.typeParameters!), /*eraseTypeParameters*/ true);
|
|
}
|
|
|
|
function getCanonicalSignature(signature: Signature): Signature {
|
|
return signature.typeParameters ?
|
|
signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) :
|
|
signature;
|
|
}
|
|
|
|
function createCanonicalSignature(signature: Signature) {
|
|
// Create an instantiation of the signature where each unconstrained type parameter is replaced with
|
|
// its original. When a generic class or interface is instantiated, each generic method in the class or
|
|
// interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios
|
|
// where different generations of the same type parameter are in scope). This leads to a lot of new type
|
|
// identities, and potentially a lot of work comparing those identities, so here we create an instantiation
|
|
// that uses the original type identities for all unconstrained type parameters.
|
|
return getSignatureInstantiation(
|
|
signature,
|
|
map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp),
|
|
isInJSFile(signature.declaration));
|
|
}
|
|
|
|
function getBaseSignature(signature: Signature) {
|
|
const typeParameters = signature.typeParameters;
|
|
if (typeParameters) {
|
|
const typeEraser = createTypeEraser(typeParameters);
|
|
const baseConstraints = map(typeParameters, tp => instantiateType(getBaseConstraintOfType(tp), typeEraser) || unknownType);
|
|
return instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true);
|
|
}
|
|
return signature;
|
|
}
|
|
|
|
function getOrCreateTypeFromSignature(signature: Signature): ObjectType {
|
|
// There are two ways to declare a construct signature, one is by declaring a class constructor
|
|
// using the constructor keyword, and the other is declaring a bare construct signature in an
|
|
// object type literal or interface (using the new keyword). Each way of declaring a constructor
|
|
// will result in a different declaration kind.
|
|
if (!signature.isolatedSignatureType) {
|
|
const kind = signature.declaration ? signature.declaration.kind : SyntaxKind.Unknown;
|
|
const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType;
|
|
const type = createObjectType(ObjectFlags.Anonymous);
|
|
type.members = emptySymbols;
|
|
type.properties = emptyArray;
|
|
type.callSignatures = !isConstructor ? [signature] : emptyArray;
|
|
type.constructSignatures = isConstructor ? [signature] : emptyArray;
|
|
signature.isolatedSignatureType = type;
|
|
}
|
|
|
|
return signature.isolatedSignatureType;
|
|
}
|
|
|
|
function getIndexSymbol(symbol: Symbol): Symbol | undefined {
|
|
return symbol.members!.get(InternalSymbolName.Index);
|
|
}
|
|
|
|
function getIndexDeclarationOfSymbol(symbol: Symbol, kind: IndexKind): IndexSignatureDeclaration | undefined {
|
|
const syntaxKind = kind === IndexKind.Number ? SyntaxKind.NumberKeyword : SyntaxKind.StringKeyword;
|
|
const indexSymbol = getIndexSymbol(symbol);
|
|
if (indexSymbol) {
|
|
for (const decl of indexSymbol.declarations) {
|
|
const node = cast(decl, isIndexSignatureDeclaration);
|
|
if (node.parameters.length === 1) {
|
|
const parameter = node.parameters[0];
|
|
if (parameter.type && parameter.type.kind === syntaxKind) {
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function createIndexInfo(type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo {
|
|
return { type, isReadonly, declaration };
|
|
}
|
|
|
|
function getIndexInfoOfSymbol(symbol: Symbol, kind: IndexKind): IndexInfo | undefined {
|
|
const declaration = getIndexDeclarationOfSymbol(symbol, kind);
|
|
if (declaration) {
|
|
return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType,
|
|
hasModifier(declaration, ModifierFlags.Readonly), declaration);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined {
|
|
return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0];
|
|
}
|
|
|
|
function getInferredTypeParameterConstraint(typeParameter: TypeParameter) {
|
|
let inferences: Type[] | undefined;
|
|
if (typeParameter.symbol) {
|
|
for (const declaration of typeParameter.symbol.declarations) {
|
|
if (declaration.parent.kind === SyntaxKind.InferType) {
|
|
// When an 'infer T' declaration is immediately contained in a type reference node
|
|
// (such as 'Foo<infer T>'), T's constraint is inferred from the constraint of the
|
|
// corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are
|
|
// present, we form an intersection of the inferred constraint types.
|
|
const grandParent = declaration.parent.parent;
|
|
if (grandParent.kind === SyntaxKind.TypeReference) {
|
|
const typeReference = <TypeReferenceNode>grandParent;
|
|
const typeParameters = getTypeParametersForTypeReference(typeReference);
|
|
if (typeParameters) {
|
|
const index = typeReference.typeArguments!.indexOf(<TypeNode>declaration.parent);
|
|
if (index < typeParameters.length) {
|
|
const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]);
|
|
if (declaredConstraint) {
|
|
// Type parameter constraints can reference other type parameters so
|
|
// constraints need to be instantiated. If instantiation produces the
|
|
// type parameter itself, we discard that inference. For example, in
|
|
// type Foo<T extends string, U extends T> = [T, U];
|
|
// type Bar<T> = T extends Foo<infer X, infer X> ? Foo<X, X> : T;
|
|
// the instantiated constraint for U is X, so we discard that inference.
|
|
const mapper = createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReference, typeParameters));
|
|
const constraint = instantiateType(declaredConstraint, mapper);
|
|
if (constraint !== typeParameter) {
|
|
inferences = append(inferences, constraint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// When an 'infer T' declaration is immediately contained in a rest parameter
|
|
// declaration, we infer an 'unknown[]' constraint.
|
|
else if (grandParent.kind === SyntaxKind.Parameter && (<ParameterDeclaration>grandParent).dotDotDotToken) {
|
|
inferences = append(inferences, createArrayType(unknownType));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return inferences && getIntersectionType(inferences);
|
|
}
|
|
|
|
/** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */
|
|
function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined {
|
|
if (!typeParameter.constraint) {
|
|
if (typeParameter.target) {
|
|
const targetConstraint = getConstraintOfTypeParameter(typeParameter.target);
|
|
typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType;
|
|
}
|
|
else {
|
|
const constraintDeclaration = getConstraintDeclaration(typeParameter);
|
|
if (!constraintDeclaration) {
|
|
typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType;
|
|
}
|
|
else {
|
|
let type = getTypeFromTypeNode(constraintDeclaration);
|
|
if (type.flags & TypeFlags.Any && type !== errorType) { // Allow errorType to propegate to keep downstream errors suppressed
|
|
// use keyofConstraintType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was),
|
|
// use unknown otherwise
|
|
type = constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType ? keyofConstraintType : unknownType;
|
|
}
|
|
typeParameter.constraint = type;
|
|
}
|
|
}
|
|
}
|
|
return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint;
|
|
}
|
|
|
|
function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined {
|
|
const tp = getDeclarationOfKind<TypeParameterDeclaration>(typeParameter.symbol, SyntaxKind.TypeParameter)!;
|
|
const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent;
|
|
return host && getSymbolOfNode(host);
|
|
}
|
|
|
|
function getTypeListId(types: readonly Type[] | undefined) {
|
|
let result = "";
|
|
if (types) {
|
|
const length = types.length;
|
|
let i = 0;
|
|
while (i < length) {
|
|
const startId = types[i].id;
|
|
let count = 1;
|
|
while (i + count < length && types[i + count].id === startId + count) {
|
|
count++;
|
|
}
|
|
if (result.length) {
|
|
result += ",";
|
|
}
|
|
result += startId;
|
|
if (count > 1) {
|
|
result += ":" + count;
|
|
}
|
|
i += count;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// This function is used to propagate certain flags when creating new object type references and union types.
|
|
// It is only necessary to do so if a constituent type might be the undefined type, the null type, the type
|
|
// of an object literal or the anyFunctionType. This is because there are operations in the type checker
|
|
// that care about the presence of such types at arbitrary depth in a containing type.
|
|
function getPropagatingFlagsOfTypes(types: readonly Type[], excludeKinds: TypeFlags): ObjectFlags {
|
|
let result: ObjectFlags = 0;
|
|
for (const type of types) {
|
|
if (!(type.flags & excludeKinds)) {
|
|
result |= getObjectFlags(type);
|
|
}
|
|
}
|
|
return result & ObjectFlags.PropagatingFlags;
|
|
}
|
|
|
|
function createTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): TypeReference {
|
|
const id = getTypeListId(typeArguments);
|
|
let type = target.instantiations.get(id);
|
|
if (!type) {
|
|
type = <TypeReference>createObjectType(ObjectFlags.Reference, target.symbol);
|
|
target.instantiations.set(id, type);
|
|
type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0;
|
|
type.target = target;
|
|
type.resolvedTypeArguments = typeArguments;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function cloneTypeReference(source: TypeReference): TypeReference {
|
|
const type = <TypeReference>createType(source.flags);
|
|
type.symbol = source.symbol;
|
|
type.objectFlags = source.objectFlags;
|
|
type.target = source.target;
|
|
type.resolvedTypeArguments = source.resolvedTypeArguments;
|
|
return type;
|
|
}
|
|
|
|
function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper): DeferredTypeReference {
|
|
const aliasSymbol = getAliasSymbolForTypeNode(node);
|
|
const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol);
|
|
const type = <DeferredTypeReference>createObjectType(ObjectFlags.Reference, target.symbol);
|
|
type.target = target;
|
|
type.node = node;
|
|
type.mapper = mapper;
|
|
type.aliasSymbol = aliasSymbol;
|
|
type.aliasTypeArguments = mapper ? instantiateTypes(aliasTypeArguments, mapper) : aliasTypeArguments;
|
|
return type;
|
|
}
|
|
|
|
function getTypeArguments(type: TypeReference): readonly Type[] {
|
|
if (!type.resolvedTypeArguments) {
|
|
if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) {
|
|
return type.target.localTypeParameters?.map(() => errorType) || emptyArray;
|
|
}
|
|
const node = type.node;
|
|
const typeArguments = !node ? emptyArray :
|
|
node.kind === SyntaxKind.TypeReference ? concatenate(type.target.outerTypeParameters, getEffectiveTypeArguments(node, type.target.localTypeParameters!)) :
|
|
node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] :
|
|
map(node.elementTypes, getTypeFromTypeNode);
|
|
if (popTypeResolution()) {
|
|
type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments;
|
|
}
|
|
else {
|
|
type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray;
|
|
error(
|
|
type.node || currentNode,
|
|
type.target.symbol
|
|
? Diagnostics.Type_arguments_for_0_circularly_reference_themselves
|
|
: Diagnostics.Tuple_type_arguments_circularly_reference_themselves
|
|
,
|
|
type.target.symbol && symbolToString(type.target.symbol)
|
|
);
|
|
}
|
|
}
|
|
return type.resolvedTypeArguments;
|
|
}
|
|
|
|
function getTypeReferenceArity(type: TypeReference): number {
|
|
return length(type.target.typeParameters);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get type from type-reference that reference to class or interface
|
|
*/
|
|
function getTypeFromClassOrInterfaceReference(node: NodeWithTypeArguments, symbol: Symbol): Type {
|
|
const type = <InterfaceType>getDeclaredTypeOfSymbol(getMergedSymbol(symbol));
|
|
const typeParameters = type.localTypeParameters;
|
|
if (typeParameters) {
|
|
const numTypeArguments = length(node.typeArguments);
|
|
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
|
|
const isJs = isInJSFile(node);
|
|
const isJsImplicitAny = !noImplicitAny && isJs;
|
|
if (!isJsImplicitAny && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) {
|
|
const missingAugmentsTag = isJs && isExpressionWithTypeArguments(node) && !isJSDocAugmentsTag(node.parent);
|
|
const diag = minTypeArgumentCount === typeParameters.length ?
|
|
missingAugmentsTag ?
|
|
Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag :
|
|
Diagnostics.Generic_type_0_requires_1_type_argument_s :
|
|
missingAugmentsTag ?
|
|
Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag :
|
|
Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments;
|
|
|
|
const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType);
|
|
error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length);
|
|
if (!isJs) {
|
|
// TODO: Adopt same permissive behavior in TS as in JS to reduce follow-on editing experience failures (requires editing fillMissingTypeArguments)
|
|
return errorType;
|
|
}
|
|
}
|
|
if (node.kind === SyntaxKind.TypeReference && isDeferredTypeReferenceNode(<TypeReferenceNode>node, length(node.typeArguments) !== typeParameters.length)) {
|
|
return createDeferredTypeReference(<GenericType>type, <TypeReferenceNode>node, /*mapper*/ undefined);
|
|
}
|
|
// 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(typeArgumentsFromTypeReferenceNode(node), typeParameters, minTypeArgumentCount, isJs));
|
|
return createTypeReference(<GenericType>type, typeArguments);
|
|
}
|
|
return checkNoTypeArguments(node, symbol) ? type : errorType;
|
|
}
|
|
|
|
function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined): 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 = instantiateType(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration)))));
|
|
}
|
|
return instantiation;
|
|
}
|
|
|
|
/**
|
|
* Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include
|
|
* references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the
|
|
* declared type. Instantiations are cached using the type identities of the type arguments as the key.
|
|
*/
|
|
function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol): Type {
|
|
const type = getDeclaredTypeOfSymbol(symbol);
|
|
const typeParameters = getSymbolLinks(symbol).typeParameters;
|
|
if (typeParameters) {
|
|
const numTypeArguments = length(node.typeArguments);
|
|
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
|
|
if (numTypeArguments < minTypeArgumentCount || 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,
|
|
symbolToString(symbol),
|
|
minTypeArgumentCount,
|
|
typeParameters.length);
|
|
return errorType;
|
|
}
|
|
return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node));
|
|
}
|
|
return checkNoTypeArguments(node, symbol) ? type : errorType;
|
|
}
|
|
|
|
function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined {
|
|
switch (node.kind) {
|
|
case SyntaxKind.TypeReference:
|
|
return node.typeName;
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
// We only support expressions that are simple qualified names. For other
|
|
// expressions this produces undefined.
|
|
const expr = node.expression;
|
|
if (isEntityNameExpression(expr)) {
|
|
return expr;
|
|
}
|
|
// fall through;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName | undefined, meaning: SymbolFlags, ignoreErrors?: boolean) {
|
|
if (!typeReferenceName) {
|
|
return unknownSymbol;
|
|
}
|
|
|
|
return resolveEntityName(typeReferenceName, meaning, ignoreErrors) || unknownSymbol;
|
|
}
|
|
|
|
function getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type {
|
|
if (symbol === unknownSymbol) {
|
|
return errorType;
|
|
}
|
|
symbol = getExpandoSymbol(symbol) || symbol;
|
|
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
|
|
return getTypeFromClassOrInterfaceReference(node, symbol);
|
|
}
|
|
if (symbol.flags & SymbolFlags.TypeAlias) {
|
|
return getTypeFromTypeAliasReference(node, symbol);
|
|
}
|
|
// Get type from reference to named type that cannot be generic (enum or type parameter)
|
|
const res = tryGetDeclaredTypeOfSymbol(symbol);
|
|
if (res) {
|
|
return checkNoTypeArguments(node, symbol) ?
|
|
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(<TypeParameter>res, node) : getRegularTypeOfLiteralType(res) :
|
|
errorType;
|
|
}
|
|
if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) {
|
|
const jsdocType = getTypeFromJSDocValueReference(node, symbol);
|
|
if (jsdocType) {
|
|
return jsdocType;
|
|
}
|
|
else {
|
|
// Resolve the type reference as a Type for the purpose of reporting errors.
|
|
resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type);
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
}
|
|
return errorType;
|
|
}
|
|
|
|
/**
|
|
* A JSdoc TypeReference may be to a value, but resolve it as a type anyway.
|
|
* Note: If the value is imported from commonjs, it should really be an alias,
|
|
* but this function's special-case code fakes alias resolution as well.
|
|
*/
|
|
function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedJSDocType) {
|
|
const valueType = getTypeOfSymbol(symbol);
|
|
let typeType = valueType;
|
|
if (symbol.valueDeclaration) {
|
|
const decl = getRootDeclaration(symbol.valueDeclaration);
|
|
let isRequireAlias = false;
|
|
if (isVariableDeclaration(decl) && decl.initializer) {
|
|
let expr = decl.initializer;
|
|
// skip past entity names, eg `require("x").a.b.c`
|
|
while (isPropertyAccessExpression(expr)) {
|
|
expr = expr.expression;
|
|
}
|
|
isRequireAlias = isCallExpression(expr) && isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !!valueType.symbol;
|
|
}
|
|
const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier;
|
|
// valueType might not have a symbol, eg, {import('./b').STRING_LITERAL}
|
|
if (valueType.symbol && (isRequireAlias || isImportTypeWithQualifier)) {
|
|
typeType = getTypeReferenceType(node, valueType.symbol);
|
|
}
|
|
}
|
|
links.resolvedJSDocType = typeType;
|
|
}
|
|
return links.resolvedJSDocType;
|
|
}
|
|
|
|
function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) {
|
|
if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === typeVariable) {
|
|
return typeVariable;
|
|
}
|
|
const id = `${getTypeId(typeVariable)}>${getTypeId(substitute)}`;
|
|
const cached = substitutionTypes.get(id);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const result = <SubstitutionType>createType(TypeFlags.Substitution);
|
|
result.typeVariable = typeVariable;
|
|
result.substitute = substitute;
|
|
substitutionTypes.set(id, result);
|
|
return result;
|
|
}
|
|
|
|
function isUnaryTupleTypeNode(node: TypeNode) {
|
|
return node.kind === SyntaxKind.TupleType && (<TupleTypeNode>node).elementTypes.length === 1;
|
|
}
|
|
|
|
function getImpliedConstraint(typeVariable: TypeVariable, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined {
|
|
return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(typeVariable, (<TupleTypeNode>checkNode).elementTypes[0], (<TupleTypeNode>extendsNode).elementTypes[0]) :
|
|
getActualTypeVariable(getTypeFromTypeNode(checkNode)) === typeVariable ? getTypeFromTypeNode(extendsNode) :
|
|
undefined;
|
|
}
|
|
|
|
function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) {
|
|
let constraints: Type[] | undefined;
|
|
while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) {
|
|
const parent = node.parent;
|
|
if (parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
|
|
const constraint = getImpliedConstraint(typeVariable, (<ConditionalTypeNode>parent).checkType, (<ConditionalTypeNode>parent).extendsType);
|
|
if (constraint) {
|
|
constraints = append(constraints, constraint);
|
|
}
|
|
}
|
|
node = parent;
|
|
}
|
|
return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable;
|
|
}
|
|
|
|
function isJSDocTypeReference(node: Node): node is TypeReferenceNode {
|
|
return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType);
|
|
}
|
|
|
|
function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: Symbol) {
|
|
if (node.typeArguments) {
|
|
error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : (<TypeReferenceNode>node).typeName ? declarationNameToString((<TypeReferenceNode>node).typeName) : anon);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): Type | undefined {
|
|
if (isIdentifier(node.typeName)) {
|
|
const typeArgs = node.typeArguments;
|
|
switch (node.typeName.escapedText) {
|
|
case "String":
|
|
checkNoTypeArguments(node);
|
|
return stringType;
|
|
case "Number":
|
|
checkNoTypeArguments(node);
|
|
return numberType;
|
|
case "Boolean":
|
|
checkNoTypeArguments(node);
|
|
return booleanType;
|
|
case "Void":
|
|
checkNoTypeArguments(node);
|
|
return voidType;
|
|
case "Undefined":
|
|
checkNoTypeArguments(node);
|
|
return undefinedType;
|
|
case "Null":
|
|
checkNoTypeArguments(node);
|
|
return nullType;
|
|
case "Function":
|
|
case "function":
|
|
checkNoTypeArguments(node);
|
|
return globalFunctionType;
|
|
case "array":
|
|
return (!typeArgs || !typeArgs.length) && !noImplicitAny ? anyArrayType : undefined;
|
|
case "promise":
|
|
return (!typeArgs || !typeArgs.length) && !noImplicitAny ? createPromiseType(anyType) : undefined;
|
|
case "Object":
|
|
if (typeArgs && typeArgs.length === 2) {
|
|
if (isJSDocIndexSignature(node)) {
|
|
const indexed = getTypeFromTypeNode(typeArgs[0]);
|
|
const target = getTypeFromTypeNode(typeArgs[1]);
|
|
const index = createIndexInfo(target, /*isReadonly*/ false);
|
|
return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexed === stringType ? index : undefined, indexed === numberType ? index : undefined);
|
|
}
|
|
return anyType;
|
|
}
|
|
checkNoTypeArguments(node);
|
|
return !noImplicitAny ? anyType : undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getTypeFromJSDocNullableTypeNode(node: JSDocNullableType) {
|
|
const type = getTypeFromTypeNode(node.type);
|
|
return strictNullChecks ? getNullableType(type, TypeFlags.Null) : type;
|
|
}
|
|
|
|
function getTypeFromTypeReference(node: TypeReferenceType): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
// handle LS queries on the `const` in `x as const` by resolving to the type of `x`
|
|
if (isConstTypeReference(node) && isAssertionExpression(node.parent)) {
|
|
links.resolvedSymbol = unknownSymbol;
|
|
return links.resolvedType = checkExpressionCached(node.parent.expression);
|
|
}
|
|
let symbol: Symbol | undefined;
|
|
let type: Type | undefined;
|
|
const meaning = SymbolFlags.Type;
|
|
if (isJSDocTypeReference(node)) {
|
|
type = getIntendedTypeFromJSDocTypeReference(node);
|
|
if (!type) {
|
|
symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning, /*ignoreErrors*/ true);
|
|
if (symbol === unknownSymbol) {
|
|
symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning | SymbolFlags.Value);
|
|
}
|
|
else {
|
|
resolveTypeReferenceName(getTypeReferenceName(node), meaning); // Resolve again to mark errors, if any
|
|
}
|
|
type = getTypeReferenceType(node, symbol);
|
|
}
|
|
}
|
|
if (!type) {
|
|
symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning);
|
|
type = getTypeReferenceType(node, symbol);
|
|
}
|
|
// Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the
|
|
// type reference in checkTypeReferenceNode.
|
|
links.resolvedSymbol = symbol;
|
|
links.resolvedType = type;
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined {
|
|
return map(node.typeArguments, getTypeFromTypeNode);
|
|
}
|
|
|
|
function getTypeFromTypeQueryNode(node: TypeQueryNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
// TypeScript 1.0 spec (April 2014): 3.6.3
|
|
// The expression is processed as an identifier expression (section 4.3)
|
|
// or property access expression(section 4.10),
|
|
// the widened type(section 3.9) of which becomes the result.
|
|
links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(checkExpression(node.exprName)));
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType {
|
|
|
|
function getTypeDeclaration(symbol: Symbol): Declaration | undefined {
|
|
const declarations = symbol.declarations;
|
|
for (const declaration of declarations) {
|
|
switch (declaration.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
return declaration;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!symbol) {
|
|
return arity ? emptyGenericType : emptyObjectType;
|
|
}
|
|
const type = getDeclaredTypeOfSymbol(symbol);
|
|
if (!(type.flags & TypeFlags.Object)) {
|
|
error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol));
|
|
return arity ? emptyGenericType : emptyObjectType;
|
|
}
|
|
if (length((<InterfaceType>type).typeParameters) !== arity) {
|
|
error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity);
|
|
return arity ? emptyGenericType : emptyObjectType;
|
|
}
|
|
return <ObjectType>type;
|
|
}
|
|
|
|
function getGlobalValueSymbol(name: __String, reportErrors: boolean): Symbol | undefined {
|
|
return getGlobalSymbol(name, SymbolFlags.Value, reportErrors ? Diagnostics.Cannot_find_global_value_0 : undefined);
|
|
}
|
|
|
|
function getGlobalTypeSymbol(name: __String, reportErrors: boolean): Symbol | undefined {
|
|
return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined);
|
|
}
|
|
|
|
function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined {
|
|
// Don't track references for global symbols anyway, so value if `isReference` is arbitrary
|
|
return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false);
|
|
}
|
|
|
|
function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType;
|
|
function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType;
|
|
function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined {
|
|
const symbol = getGlobalTypeSymbol(name, reportErrors);
|
|
return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined;
|
|
}
|
|
|
|
function getGlobalTypedPropertyDescriptorType() {
|
|
return deferredGlobalTypedPropertyDescriptorType || (deferredGlobalTypedPropertyDescriptorType = getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalTemplateStringsArrayType() {
|
|
return deferredGlobalTemplateStringsArrayType || (deferredGlobalTemplateStringsArrayType = getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType;
|
|
}
|
|
|
|
function getGlobalImportMetaType() {
|
|
return deferredGlobalImportMetaType || (deferredGlobalImportMetaType = getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType;
|
|
}
|
|
|
|
function getGlobalESSymbolConstructorSymbol(reportErrors: boolean) {
|
|
return deferredGlobalESSymbolConstructorSymbol || (deferredGlobalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol" as __String, reportErrors));
|
|
}
|
|
|
|
function getGlobalESSymbolType(reportErrors: boolean) {
|
|
return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
|
|
}
|
|
|
|
function getGlobalPromiseType(reportErrors: boolean) {
|
|
return deferredGlobalPromiseType || (deferredGlobalPromiseType = getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalPromiseLikeType(reportErrors: boolean) {
|
|
return deferredGlobalPromiseLikeType || (deferredGlobalPromiseLikeType = getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined {
|
|
return deferredGlobalPromiseConstructorSymbol || (deferredGlobalPromiseConstructorSymbol = getGlobalValueSymbol("Promise" as __String, reportErrors));
|
|
}
|
|
|
|
function getGlobalPromiseConstructorLikeType(reportErrors: boolean) {
|
|
return deferredGlobalPromiseConstructorLikeType || (deferredGlobalPromiseConstructorLikeType = getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
|
|
}
|
|
|
|
function getGlobalAsyncIterableType(reportErrors: boolean) {
|
|
return deferredGlobalAsyncIterableType || (deferredGlobalAsyncIterableType = getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalAsyncIteratorType(reportErrors: boolean) {
|
|
return deferredGlobalAsyncIteratorType || (deferredGlobalAsyncIteratorType = getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalAsyncIterableIteratorType(reportErrors: boolean) {
|
|
return deferredGlobalAsyncIterableIteratorType || (deferredGlobalAsyncIterableIteratorType = getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalAsyncGeneratorType(reportErrors: boolean) {
|
|
return deferredGlobalAsyncGeneratorType || (deferredGlobalAsyncGeneratorType = getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalIterableType(reportErrors: boolean) {
|
|
return deferredGlobalIterableType || (deferredGlobalIterableType = getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalIteratorType(reportErrors: boolean) {
|
|
return deferredGlobalIteratorType || (deferredGlobalIteratorType = getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalIterableIteratorType(reportErrors: boolean) {
|
|
return deferredGlobalIterableIteratorType || (deferredGlobalIterableIteratorType = getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalGeneratorType(reportErrors: boolean) {
|
|
return deferredGlobalGeneratorType || (deferredGlobalGeneratorType = getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalIteratorYieldResultType(reportErrors: boolean) {
|
|
return deferredGlobalIteratorYieldResultType || (deferredGlobalIteratorYieldResultType = getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalIteratorReturnResultType(reportErrors: boolean) {
|
|
return deferredGlobalIteratorReturnResultType || (deferredGlobalIteratorReturnResultType = getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
|
|
}
|
|
|
|
function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined {
|
|
const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined);
|
|
return symbol && <GenericType>getTypeOfGlobalSymbol(symbol, arity);
|
|
}
|
|
|
|
function getGlobalExtractSymbol(): Symbol {
|
|
return deferredGlobalExtractSymbol || (deferredGlobalExtractSymbol = getGlobalSymbol("Extract" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217
|
|
}
|
|
|
|
function getGlobalOmitSymbol(): Symbol {
|
|
return deferredGlobalOmitSymbol || (deferredGlobalOmitSymbol = getGlobalSymbol("Omit" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217
|
|
}
|
|
|
|
function getGlobalBigIntType(reportErrors: boolean) {
|
|
return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
|
|
}
|
|
|
|
/**
|
|
* Instantiates a global type that is generic with some element type, and returns that instantiation.
|
|
*/
|
|
function createTypeFromGenericGlobalType(genericGlobalType: GenericType, typeArguments: readonly Type[]): ObjectType {
|
|
return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType;
|
|
}
|
|
|
|
function createTypedPropertyDescriptorType(propertyType: Type): Type {
|
|
return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]);
|
|
}
|
|
|
|
function createIterableType(iteratedType: Type): Type {
|
|
return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]);
|
|
}
|
|
|
|
function createArrayType(elementType: Type, readonly?: boolean): ObjectType {
|
|
return createTypeFromGenericGlobalType(readonly ? globalReadonlyArrayType : globalArrayType, [elementType]);
|
|
}
|
|
|
|
function getArrayOrTupleTargetType(node: ArrayTypeNode | TupleTypeNode): GenericType {
|
|
const readonly = isReadonlyTypeOperator(node.parent);
|
|
if (node.kind === SyntaxKind.ArrayType || node.elementTypes.length === 1 && node.elementTypes[0].kind === SyntaxKind.RestType) {
|
|
return readonly ? globalReadonlyArrayType : globalArrayType;
|
|
}
|
|
const lastElement = lastOrUndefined(node.elementTypes);
|
|
const restElement = lastElement && lastElement.kind === SyntaxKind.RestType ? lastElement : undefined;
|
|
const minLength = findLastIndex(node.elementTypes, n => n.kind !== SyntaxKind.OptionalType && n !== restElement) + 1;
|
|
return getTupleTypeOfArity(node.elementTypes.length, minLength, !!restElement, readonly, /*associatedNames*/ undefined);
|
|
}
|
|
|
|
// Return true if the given type reference node is directly aliased or if it needs to be deferred
|
|
// because it is possibly contained in a circular chain of eagerly resolved types.
|
|
function isDeferredTypeReferenceNode(node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, hasDefaultTypeArguments?: boolean) {
|
|
return !!getAliasSymbolForTypeNode(node) || isResolvedByTypeAlias(node) && (
|
|
node.kind === SyntaxKind.ArrayType ? mayResolveTypeAlias(node.elementType) :
|
|
node.kind === SyntaxKind.TupleType ? some(node.elementTypes, mayResolveTypeAlias) :
|
|
hasDefaultTypeArguments || some(node.typeArguments, mayResolveTypeAlias));
|
|
}
|
|
|
|
// Return true when the given node is transitively contained in type constructs that eagerly
|
|
// resolve their constituent types. We include SyntaxKind.TypeReference because type arguments
|
|
// of type aliases are eagerly resolved.
|
|
function isResolvedByTypeAlias(node: Node): boolean {
|
|
const parent = node.parent;
|
|
switch (parent.kind) {
|
|
case SyntaxKind.ParenthesizedType:
|
|
case SyntaxKind.TypeReference:
|
|
case SyntaxKind.UnionType:
|
|
case SyntaxKind.IntersectionType:
|
|
case SyntaxKind.IndexedAccessType:
|
|
case SyntaxKind.ConditionalType:
|
|
case SyntaxKind.TypeOperator:
|
|
return isResolvedByTypeAlias(parent);
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Return true if resolving the given node (i.e. getTypeFromTypeNode) possibly causes resolution
|
|
// of a type alias.
|
|
function mayResolveTypeAlias(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.TypeReference:
|
|
return isJSDocTypeReference(node) || !!(resolveTypeReferenceName((<TypeReferenceNode>node).typeName, SymbolFlags.Type).flags & SymbolFlags.TypeAlias);
|
|
case SyntaxKind.TypeQuery:
|
|
return true;
|
|
case SyntaxKind.TypeOperator:
|
|
return (<TypeOperatorNode>node).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((<TypeOperatorNode>node).type);
|
|
case SyntaxKind.ParenthesizedType:
|
|
case SyntaxKind.OptionalType:
|
|
case SyntaxKind.JSDocOptionalType:
|
|
case SyntaxKind.JSDocNullableType:
|
|
case SyntaxKind.JSDocNonNullableType:
|
|
case SyntaxKind.JSDocTypeExpression:
|
|
return mayResolveTypeAlias((<ParenthesizedTypeNode | OptionalTypeNode | JSDocTypeReferencingNode>node).type);
|
|
case SyntaxKind.RestType:
|
|
return (<RestTypeNode>node).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias((<ArrayTypeNode>(<RestTypeNode>node).type).elementType);
|
|
case SyntaxKind.UnionType:
|
|
case SyntaxKind.IntersectionType:
|
|
return some((<UnionOrIntersectionTypeNode>node).types, mayResolveTypeAlias);
|
|
case SyntaxKind.IndexedAccessType:
|
|
return mayResolveTypeAlias((<IndexedAccessTypeNode>node).objectType) || mayResolveTypeAlias((<IndexedAccessTypeNode>node).indexType);
|
|
case SyntaxKind.ConditionalType:
|
|
return mayResolveTypeAlias((<ConditionalTypeNode>node).checkType) || mayResolveTypeAlias((<ConditionalTypeNode>node).extendsType) ||
|
|
mayResolveTypeAlias((<ConditionalTypeNode>node).trueType) || mayResolveTypeAlias((<ConditionalTypeNode>node).falseType);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
const target = getArrayOrTupleTargetType(node);
|
|
if (target === emptyGenericType) {
|
|
links.resolvedType = emptyObjectType;
|
|
}
|
|
else if (isDeferredTypeReferenceNode(node)) {
|
|
links.resolvedType = node.kind === SyntaxKind.TupleType && node.elementTypes.length === 0 ? target :
|
|
createDeferredTypeReference(target, node, /*mapper*/ undefined);
|
|
}
|
|
else {
|
|
const elementTypes = node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elementTypes, getTypeFromTypeNode);
|
|
links.resolvedType = createTypeReference(target, elementTypes);
|
|
}
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function isReadonlyTypeOperator(node: Node) {
|
|
return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword;
|
|
}
|
|
|
|
// We represent tuple types as type references to synthesized generic interface types created by
|
|
// this function. The types are of the form:
|
|
//
|
|
// interface Tuple<T0, T1, T2, ...> extends Array<T0 | T1 | T2 | ...> { 0: T0, 1: T1, 2: T2, ... }
|
|
//
|
|
// Note that the generic type created by this function has no symbol associated with it. The same
|
|
// is true for each of the synthesized type parameters.
|
|
function createTupleTypeOfArity(arity: number, minLength: number, hasRestElement: boolean, readonly: boolean, associatedNames: __String[] | undefined): TupleType {
|
|
let typeParameters: TypeParameter[] | undefined;
|
|
const properties: Symbol[] = [];
|
|
const maxLength = hasRestElement ? arity - 1 : arity;
|
|
if (arity) {
|
|
typeParameters = new Array(arity);
|
|
for (let i = 0; i < arity; i++) {
|
|
const typeParameter = typeParameters[i] = createTypeParameter();
|
|
if (i < maxLength) {
|
|
const property = createSymbol(SymbolFlags.Property | (i >= minLength ? SymbolFlags.Optional : 0),
|
|
"" + i as __String, readonly ? CheckFlags.Readonly : 0);
|
|
property.type = typeParameter;
|
|
properties.push(property);
|
|
}
|
|
}
|
|
}
|
|
const literalTypes = [];
|
|
for (let i = minLength; i <= maxLength; i++) literalTypes.push(getLiteralType(i));
|
|
const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String);
|
|
lengthSymbol.type = hasRestElement ? numberType : getUnionType(literalTypes);
|
|
properties.push(lengthSymbol);
|
|
const type = <TupleType & InterfaceTypeWithDeclaredMembers>createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference);
|
|
type.typeParameters = typeParameters;
|
|
type.outerTypeParameters = undefined;
|
|
type.localTypeParameters = typeParameters;
|
|
type.instantiations = createMap<TypeReference>();
|
|
type.instantiations.set(getTypeListId(type.typeParameters), <GenericType>type);
|
|
type.target = <GenericType>type;
|
|
type.resolvedTypeArguments = type.typeParameters;
|
|
type.thisType = createTypeParameter();
|
|
type.thisType.isThisType = true;
|
|
type.thisType.constraint = type;
|
|
type.declaredProperties = properties;
|
|
type.declaredCallSignatures = emptyArray;
|
|
type.declaredConstructSignatures = emptyArray;
|
|
type.declaredStringIndexInfo = undefined;
|
|
type.declaredNumberIndexInfo = undefined;
|
|
type.minLength = minLength;
|
|
type.hasRestElement = hasRestElement;
|
|
type.readonly = readonly;
|
|
type.associatedNames = associatedNames;
|
|
return type;
|
|
}
|
|
|
|
function getTupleTypeOfArity(arity: number, minLength: number, hasRestElement: boolean, readonly: boolean, associatedNames?: __String[]): GenericType {
|
|
const key = arity + (hasRestElement ? "+" : ",") + minLength + (readonly ? "R" : "") + (associatedNames && associatedNames.length ? "," + associatedNames.join(",") : "");
|
|
let type = tupleTypes.get(key);
|
|
if (!type) {
|
|
tupleTypes.set(key, type = createTupleTypeOfArity(arity, minLength, hasRestElement, readonly, associatedNames));
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function createTupleType(elementTypes: readonly Type[], minLength = elementTypes.length, hasRestElement = false, readonly = false, associatedNames?: __String[]) {
|
|
const arity = elementTypes.length;
|
|
if (arity === 1 && hasRestElement) {
|
|
return createArrayType(elementTypes[0], readonly);
|
|
}
|
|
const tupleType = getTupleTypeOfArity(arity, minLength, arity > 0 && hasRestElement, readonly, associatedNames);
|
|
return elementTypes.length ? createTypeReference(tupleType, elementTypes) : tupleType;
|
|
}
|
|
|
|
function sliceTupleType(type: TupleTypeReference, index: number) {
|
|
const tuple = type.target;
|
|
if (tuple.hasRestElement) {
|
|
// don't slice off rest element
|
|
index = Math.min(index, getTypeReferenceArity(type) - 1);
|
|
}
|
|
return createTupleType(
|
|
getTypeArguments(type).slice(index),
|
|
Math.max(0, tuple.minLength - index),
|
|
tuple.hasRestElement,
|
|
tuple.readonly,
|
|
tuple.associatedNames && tuple.associatedNames.slice(index),
|
|
);
|
|
}
|
|
|
|
function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type {
|
|
const type = getTypeFromTypeNode(node.type);
|
|
return strictNullChecks ? getOptionalType(type) : type;
|
|
}
|
|
|
|
function getTypeId(type: Type) {
|
|
return type.id;
|
|
}
|
|
|
|
function containsType(types: readonly Type[], type: Type): boolean {
|
|
return binarySearch(types, type, getTypeId, compareValues) >= 0;
|
|
}
|
|
|
|
function insertType(types: Type[], type: Type): boolean {
|
|
const index = binarySearch(types, type, getTypeId, compareValues);
|
|
if (index < 0) {
|
|
types.splice(~index, 0, type);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) {
|
|
const flags = type.flags;
|
|
if (flags & TypeFlags.Union) {
|
|
return addTypesToUnion(typeSet, includes, (<UnionType>type).types);
|
|
}
|
|
// We ignore 'never' types in unions
|
|
if (!(flags & TypeFlags.Never)) {
|
|
includes |= flags & TypeFlags.IncludesMask;
|
|
if (flags & TypeFlags.StructuredOrInstantiable) includes |= TypeFlags.IncludesStructuredOrInstantiable;
|
|
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
|
|
if (!strictNullChecks && flags & TypeFlags.Nullable) {
|
|
if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType;
|
|
}
|
|
else {
|
|
const len = typeSet.length;
|
|
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues);
|
|
if (index < 0) {
|
|
typeSet.splice(~index, 0, type);
|
|
}
|
|
}
|
|
}
|
|
return includes;
|
|
}
|
|
|
|
// Add the given types to the given type set. Order is preserved, duplicates are removed,
|
|
// and nested types of the given kind are flattened into the set.
|
|
function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags {
|
|
for (const type of types) {
|
|
includes = addTypeToUnion(typeSet, includes, type);
|
|
}
|
|
return includes;
|
|
}
|
|
|
|
function isSetOfLiteralsFromSameEnum(types: readonly Type[]): boolean {
|
|
const first = types[0];
|
|
if (first.flags & TypeFlags.EnumLiteral) {
|
|
const firstEnum = getParentOfSymbol(first.symbol);
|
|
for (let i = 1; i < types.length; i++) {
|
|
const other = types[i];
|
|
if (!(other.flags & TypeFlags.EnumLiteral) || (firstEnum !== getParentOfSymbol(other.symbol))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function removeSubtypes(types: Type[], primitivesOnly: boolean): boolean {
|
|
const len = types.length;
|
|
if (len === 0 || isSetOfLiteralsFromSameEnum(types)) {
|
|
return true;
|
|
}
|
|
let i = len;
|
|
let count = 0;
|
|
while (i > 0) {
|
|
i--;
|
|
const source = types[i];
|
|
for (const target of types) {
|
|
if (source !== target) {
|
|
if (count === 100000) {
|
|
// After 100000 subtype checks we estimate the remaining amount of work by assuming the
|
|
// same ratio of checks per element. If the estimated number of remaining type checks is
|
|
// greater than an upper limit we deem the union type too complex to represent. The
|
|
// upper limit is 25M for unions of primitives only, and 1M otherwise. This for example
|
|
// caps union types at 5000 unique literal types and 1000 unique object types.
|
|
const estimatedCount = (count / (len - i)) * len;
|
|
if (estimatedCount > (primitivesOnly ? 25000000 : 1000000)) {
|
|
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
|
|
return false;
|
|
}
|
|
}
|
|
count++;
|
|
if (isTypeRelatedTo(source, target, strictSubtypeRelation) && (
|
|
!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) ||
|
|
!(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) ||
|
|
isTypeDerivedFrom(source, target))) {
|
|
orderedRemoveItemAt(types, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags) {
|
|
let i = types.length;
|
|
while (i > 0) {
|
|
i--;
|
|
const t = types[i];
|
|
const remove =
|
|
t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String ||
|
|
t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number ||
|
|
t.flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt ||
|
|
t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol ||
|
|
isFreshLiteralType(t) && containsType(types, (<LiteralType>t).regularType);
|
|
if (remove) {
|
|
orderedRemoveItemAt(types, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We sort and deduplicate the constituent types based on object identity. If the subtypeReduction
|
|
// flag is specified we also reduce the constituent type set to only include types that aren't subtypes
|
|
// of other types. Subtype reduction is expensive for large union types and is possible only when union
|
|
// types are known not to circularly reference themselves (as is the case with union types created by
|
|
// expression constructs such as array literals and the || and ?: operators). Named types can
|
|
// circularly reference themselves and therefore cannot be subtype reduced during their declaration.
|
|
// For example, "type Item = string | (() => Item" is a named type that circularly references itself.
|
|
function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
|
|
if (types.length === 0) {
|
|
return neverType;
|
|
}
|
|
if (types.length === 1) {
|
|
return types[0];
|
|
}
|
|
const typeSet: Type[] = [];
|
|
const includes = addTypesToUnion(typeSet, 0, types);
|
|
if (unionReduction !== UnionReduction.None) {
|
|
if (includes & TypeFlags.AnyOrUnknown) {
|
|
return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType;
|
|
}
|
|
switch (unionReduction) {
|
|
case UnionReduction.Literal:
|
|
if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol)) {
|
|
removeRedundantLiteralTypes(typeSet, includes);
|
|
}
|
|
break;
|
|
case UnionReduction.Subtype:
|
|
if (!removeSubtypes(typeSet, !(includes & TypeFlags.IncludesStructuredOrInstantiable))) {
|
|
return errorType;
|
|
}
|
|
break;
|
|
}
|
|
if (typeSet.length === 0) {
|
|
return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType :
|
|
includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType :
|
|
neverType;
|
|
}
|
|
}
|
|
const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) |
|
|
(includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0);
|
|
return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments);
|
|
}
|
|
|
|
function getUnionTypePredicate(signatures: readonly Signature[]): TypePredicate | undefined {
|
|
let first: TypePredicate | undefined;
|
|
const types: Type[] = [];
|
|
for (const sig of signatures) {
|
|
const pred = getTypePredicateOfSignature(sig);
|
|
if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) {
|
|
continue;
|
|
}
|
|
|
|
if (first) {
|
|
if (!typePredicateKindsMatch(first, pred)) {
|
|
// No common type predicate.
|
|
return undefined;
|
|
}
|
|
}
|
|
else {
|
|
first = pred;
|
|
}
|
|
types.push(pred.type);
|
|
}
|
|
if (!first) {
|
|
// No union signatures had a type predicate.
|
|
return undefined;
|
|
}
|
|
const unionType = getUnionType(types);
|
|
return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, unionType);
|
|
}
|
|
|
|
function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean {
|
|
return a.kind === b.kind && a.parameterIndex === b.parameterIndex;
|
|
}
|
|
|
|
// This function assumes the constituent type list is sorted and deduplicated.
|
|
function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
|
|
if (types.length === 0) {
|
|
return neverType;
|
|
}
|
|
if (types.length === 1) {
|
|
return types[0];
|
|
}
|
|
const id = getTypeListId(types);
|
|
let type = unionTypes.get(id);
|
|
if (!type) {
|
|
type = <UnionType>createType(TypeFlags.Union);
|
|
unionTypes.set(id, type);
|
|
type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
|
|
type.types = types;
|
|
/*
|
|
Note: This is the alias symbol (or lack thereof) that we see when we first encounter this union type.
|
|
For aliases of identical unions, eg `type T = A | B; type U = A | B`, the symbol of the first alias encountered is the aliasSymbol.
|
|
(In the language service, the order may depend on the order in which a user takes actions, such as hovering over symbols.)
|
|
It's important that we create equivalent union types only once, so that's an unfortunate side effect.
|
|
*/
|
|
type.aliasSymbol = aliasSymbol;
|
|
type.aliasTypeArguments = aliasTypeArguments;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
const aliasSymbol = getAliasSymbolForTypeNode(node);
|
|
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal,
|
|
aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol));
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function addTypeToIntersection(typeSet: Map<Type>, includes: TypeFlags, type: Type) {
|
|
const flags = type.flags;
|
|
if (flags & TypeFlags.Intersection) {
|
|
return addTypesToIntersection(typeSet, includes, (<IntersectionType>type).types);
|
|
}
|
|
if (isEmptyAnonymousObjectType(type)) {
|
|
if (!(includes & TypeFlags.IncludesEmptyObject)) {
|
|
includes |= TypeFlags.IncludesEmptyObject;
|
|
typeSet.set(type.id.toString(), type);
|
|
}
|
|
}
|
|
else {
|
|
if (flags & TypeFlags.AnyOrUnknown) {
|
|
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
|
|
}
|
|
else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !typeSet.has(type.id.toString())) {
|
|
if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) {
|
|
// We have seen two distinct unit types which means we should reduce to an
|
|
// empty intersection. Adding TypeFlags.NonPrimitive causes that to happen.
|
|
includes |= TypeFlags.NonPrimitive;
|
|
}
|
|
typeSet.set(type.id.toString(), type);
|
|
}
|
|
includes |= flags & TypeFlags.IncludesMask;
|
|
}
|
|
return includes;
|
|
}
|
|
|
|
// Add the given types to the given type set. Order is preserved, freshness is removed from literal
|
|
// types, duplicates are removed, and nested types of the given kind are flattened into the set.
|
|
function addTypesToIntersection(typeSet: Map<Type>, includes: TypeFlags, types: readonly Type[]) {
|
|
for (const type of types) {
|
|
includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type));
|
|
}
|
|
return includes;
|
|
}
|
|
|
|
function removeRedundantPrimitiveTypes(types: Type[], includes: TypeFlags) {
|
|
let i = types.length;
|
|
while (i > 0) {
|
|
i--;
|
|
const t = types[i];
|
|
const remove =
|
|
t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral ||
|
|
t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral ||
|
|
t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral ||
|
|
t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol;
|
|
if (remove) {
|
|
orderedRemoveItemAt(types, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check that the given type has a match in every union. A given type is matched by
|
|
// an identical type, and a literal type is additionally matched by its corresponding
|
|
// primitive type.
|
|
function eachUnionContains(unionTypes: UnionType[], type: Type) {
|
|
for (const u of unionTypes) {
|
|
if (!containsType(u.types, type)) {
|
|
const primitive = type.flags & TypeFlags.StringLiteral ? stringType :
|
|
type.flags & TypeFlags.NumberLiteral ? numberType :
|
|
type.flags & TypeFlags.BigIntLiteral ? bigintType :
|
|
type.flags & TypeFlags.UniqueESSymbol ? esSymbolType :
|
|
undefined;
|
|
if (!primitive || !containsType(u.types, primitive)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function extractIrreducible(types: Type[], flag: TypeFlags) {
|
|
if (every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)))) {
|
|
for (let i = 0; i < types.length; i++) {
|
|
types[i] = filterType(types[i], t => !(t.flags & flag));
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// If the given list of types contains more than one union of primitive types, replace the
|
|
// first with a union containing an intersection of those primitive types, then remove the
|
|
// other unions and return true. Otherwise, do nothing and return false.
|
|
function intersectUnionsOfPrimitiveTypes(types: Type[]) {
|
|
let unionTypes: UnionType[] | undefined;
|
|
const index = findIndex(types, t => !!(getObjectFlags(t) & ObjectFlags.PrimitiveUnion));
|
|
if (index < 0) {
|
|
return false;
|
|
}
|
|
let i = index + 1;
|
|
// Remove all but the first union of primitive types and collect them in
|
|
// the unionTypes array.
|
|
while (i < types.length) {
|
|
const t = types[i];
|
|
if (getObjectFlags(t) & ObjectFlags.PrimitiveUnion) {
|
|
(unionTypes || (unionTypes = [<UnionType>types[index]])).push(<UnionType>t);
|
|
orderedRemoveItemAt(types, i);
|
|
}
|
|
else {
|
|
i++;
|
|
}
|
|
}
|
|
// Return false if there was only one union of primitive types
|
|
if (!unionTypes) {
|
|
return false;
|
|
}
|
|
// We have more than one union of primitive types, now intersect them. For each
|
|
// type in each union we check if the type is matched in every union and if so
|
|
// we include it in the result.
|
|
const checked: Type[] = [];
|
|
const result: Type[] = [];
|
|
for (const u of unionTypes) {
|
|
for (const t of u.types) {
|
|
if (insertType(checked, t)) {
|
|
if (eachUnionContains(unionTypes, t)) {
|
|
insertType(result, t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Finally replace the first union with the result
|
|
types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion);
|
|
return true;
|
|
}
|
|
|
|
function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) {
|
|
const result = <IntersectionType>createType(TypeFlags.Intersection);
|
|
result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
|
|
result.types = types;
|
|
result.aliasSymbol = aliasSymbol; // See comment in `getUnionTypeFromSortedList`.
|
|
result.aliasTypeArguments = aliasTypeArguments;
|
|
return result;
|
|
}
|
|
|
|
// We normalize combinations of intersection and union types based on the distributive property of the '&'
|
|
// operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection
|
|
// types with union type constituents into equivalent union types with intersection type constituents and
|
|
// effectively ensure that union types are always at the top level in type representations.
|
|
//
|
|
// We do not perform structural deduplication on intersection types. Intersection types are created only by the &
|
|
// type operator and we can't reduce those because we want to support recursive intersection types. For example,
|
|
// a type alias of the form "type List<T> = T & { next: List<T> }" cannot be reduced during its declaration.
|
|
// Also, unlike union types, the order of the constituent types is preserved in order that overload resolution
|
|
// for intersections of types with signatures can be deterministic.
|
|
function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
|
|
const typeMembershipMap: Map<Type> = createMap();
|
|
const includes = addTypesToIntersection(typeMembershipMap, 0, types);
|
|
const typeSet: Type[] = arrayFrom(typeMembershipMap.values());
|
|
// An intersection type is considered empty if it contains
|
|
// the type never, or
|
|
// more than one unit type or,
|
|
// an object type and a nullable type (null or undefined), or
|
|
// a string-like type and a type known to be non-string-like, or
|
|
// a number-like type and a type known to be non-number-like, or
|
|
// a symbol-like type and a type known to be non-symbol-like, or
|
|
// a void-like type and a type known to be non-void-like, or
|
|
// a non-primitive type and a type known to be primitive.
|
|
if (includes & TypeFlags.Never ||
|
|
strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) ||
|
|
includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) ||
|
|
includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) ||
|
|
includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) ||
|
|
includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) ||
|
|
includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) ||
|
|
includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) {
|
|
return neverType;
|
|
}
|
|
if (includes & TypeFlags.Any) {
|
|
return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType;
|
|
}
|
|
if (!strictNullChecks && includes & TypeFlags.Nullable) {
|
|
return includes & TypeFlags.Undefined ? undefinedType : nullType;
|
|
}
|
|
if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral ||
|
|
includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral ||
|
|
includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral ||
|
|
includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) {
|
|
removeRedundantPrimitiveTypes(typeSet, includes);
|
|
}
|
|
if (includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.Object) {
|
|
orderedRemoveItemAt(typeSet, findIndex(typeSet, isEmptyAnonymousObjectType));
|
|
}
|
|
if (typeSet.length === 0) {
|
|
return unknownType;
|
|
}
|
|
if (typeSet.length === 1) {
|
|
return typeSet[0];
|
|
}
|
|
const id = getTypeListId(typeSet);
|
|
let result = intersectionTypes.get(id);
|
|
if (!result) {
|
|
if (includes & TypeFlags.Union) {
|
|
if (intersectUnionsOfPrimitiveTypes(typeSet)) {
|
|
// When the intersection creates a reduced set (which might mean that *all* union types have
|
|
// disappeared), we restart the operation to get a new set of combined flags. Once we have
|
|
// reduced we'll never reduce again, so this occurs at most once.
|
|
result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
|
|
}
|
|
else if (extractIrreducible(typeSet, TypeFlags.Undefined)) {
|
|
result = getUnionType([getIntersectionType(typeSet), undefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
|
|
}
|
|
else if (extractIrreducible(typeSet, TypeFlags.Null)) {
|
|
result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
|
|
}
|
|
else {
|
|
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
|
|
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
|
|
// If the estimated size of the resulting union type exceeds 100000 constituents, report an error.
|
|
const size = reduceLeft(typeSet, (n, t) => n * (t.flags & TypeFlags.Union ? (<UnionType>t).types.length : 1), 1);
|
|
if (size >= 100000) {
|
|
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
|
|
return errorType;
|
|
}
|
|
const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0);
|
|
const unionType = <UnionType>typeSet[unionIndex];
|
|
result = getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
|
|
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
|
|
}
|
|
}
|
|
else {
|
|
result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
|
|
}
|
|
intersectionTypes.set(id, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
const aliasSymbol = getAliasSymbolForTypeNode(node);
|
|
links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode),
|
|
aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol));
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) {
|
|
const result = <IndexType>createType(TypeFlags.Index);
|
|
result.type = type;
|
|
result.stringsOnly = stringsOnly;
|
|
return result;
|
|
}
|
|
|
|
function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) {
|
|
return stringsOnly ?
|
|
type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) :
|
|
type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false));
|
|
}
|
|
|
|
function getLiteralTypeFromPropertyName(name: PropertyName) {
|
|
if (isPrivateIdentifier(name)) {
|
|
return neverType;
|
|
}
|
|
return isIdentifier(name) ? getLiteralType(unescapeLeadingUnderscores(name.escapedText)) :
|
|
getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name));
|
|
}
|
|
|
|
function getBigIntLiteralType(node: BigIntLiteral): LiteralType {
|
|
return getLiteralType({
|
|
negative: false,
|
|
base10Value: parsePseudoBigInt(node.text)
|
|
});
|
|
}
|
|
|
|
function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags) {
|
|
if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
|
|
let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType;
|
|
if (!type && !isKnownSymbol(prop)) {
|
|
if (prop.escapedName === InternalSymbolName.Default) {
|
|
type = getLiteralType("default");
|
|
}
|
|
else {
|
|
const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration) as PropertyName;
|
|
type = name && getLiteralTypeFromPropertyName(name) || getLiteralType(symbolName(prop));
|
|
}
|
|
}
|
|
if (type && type.flags & include) {
|
|
return type;
|
|
}
|
|
}
|
|
return neverType;
|
|
}
|
|
|
|
function getLiteralTypeFromProperties(type: Type, include: TypeFlags) {
|
|
return getUnionType(map(getPropertiesOfType(type), p => getLiteralTypeFromProperty(p, include)));
|
|
}
|
|
|
|
function getNonEnumNumberIndexInfo(type: Type) {
|
|
const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number);
|
|
return numberIndexInfo !== enumNumberIndexInfo ? numberIndexInfo : undefined;
|
|
}
|
|
|
|
function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type {
|
|
type = getReducedType(type);
|
|
return type.flags & TypeFlags.Union ? getIntersectionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
|
|
type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
|
|
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
|
|
getObjectFlags(type) & ObjectFlags.Mapped ? filterType(getConstraintTypeFromMappedType(<MappedType>type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String))) :
|
|
type === wildcardType ? wildcardType :
|
|
type.flags & TypeFlags.Unknown ? neverType :
|
|
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
|
|
stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
|
|
!noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
|
|
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
|
|
getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique);
|
|
}
|
|
|
|
function getExtractStringType(type: Type) {
|
|
if (keyofStringsOnly) {
|
|
return type;
|
|
}
|
|
const extractTypeAlias = getGlobalExtractSymbol();
|
|
return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType;
|
|
}
|
|
|
|
function getIndexTypeOrString(type: Type): Type {
|
|
const indexType = getExtractStringType(getIndexType(type));
|
|
return indexType.flags & TypeFlags.Never ? stringType : indexType;
|
|
}
|
|
|
|
function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
switch (node.operator) {
|
|
case SyntaxKind.KeyOfKeyword:
|
|
links.resolvedType = getIndexType(getTypeFromTypeNode(node.type));
|
|
break;
|
|
case SyntaxKind.UniqueKeyword:
|
|
links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword
|
|
? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent))
|
|
: errorType;
|
|
break;
|
|
case SyntaxKind.ReadonlyKeyword:
|
|
links.resolvedType = getTypeFromTypeNode(node.type);
|
|
break;
|
|
default:
|
|
throw Debug.assertNever(node.operator);
|
|
}
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function createIndexedAccessType(objectType: Type, indexType: Type) {
|
|
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
|
|
type.objectType = objectType;
|
|
type.indexType = indexType;
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Returns if a type is or consists of a JSLiteral object type
|
|
* In addition to objects which are directly literals,
|
|
* * unions where every element is a jsliteral
|
|
* * intersections where at least one element is a jsliteral
|
|
* * and instantiable types constrained to a jsliteral
|
|
* Should all count as literals and not print errors on access or assignment of possibly existing properties.
|
|
* This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference).
|
|
*/
|
|
function isJSLiteralType(type: Type): boolean {
|
|
if (noImplicitAny) {
|
|
return false; // Flag is meaningless under `noImplicitAny` mode
|
|
}
|
|
if (getObjectFlags(type) & ObjectFlags.JSLiteral) {
|
|
return true;
|
|
}
|
|
if (type.flags & TypeFlags.Union) {
|
|
return every((type as UnionType).types, isJSLiteralType);
|
|
}
|
|
if (type.flags & TypeFlags.Intersection) {
|
|
return some((type as IntersectionType).types, isJSLiteralType);
|
|
}
|
|
if (type.flags & TypeFlags.Instantiable) {
|
|
return isJSLiteralType(getResolvedBaseConstraint(type));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) {
|
|
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
|
|
return isTypeUsableAsPropertyName(indexType) ?
|
|
getPropertyNameFromType(indexType) :
|
|
accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ?
|
|
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) :
|
|
accessNode && isPropertyName(accessNode) ?
|
|
// late bound names are handled in the first branch, so here we only need to handle normal names
|
|
getPropertyNameForPropertyNameNode(accessNode) :
|
|
undefined;
|
|
}
|
|
|
|
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
|
|
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
|
|
const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode);
|
|
if (propName !== undefined) {
|
|
const prop = getPropertyOfType(objectType, propName);
|
|
if (prop) {
|
|
if (accessExpression) {
|
|
markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword);
|
|
if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) {
|
|
error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop));
|
|
return undefined;
|
|
}
|
|
if (accessFlags & AccessFlags.CacheSymbol) {
|
|
getNodeLinks(accessNode!).resolvedSymbol = prop;
|
|
}
|
|
}
|
|
const propType = getTypeOfSymbol(prop);
|
|
return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ?
|
|
getFlowTypeOfReference(accessExpression, propType) :
|
|
propType;
|
|
}
|
|
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
|
|
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) {
|
|
const indexNode = getIndexNodeForAccessExpression(accessNode);
|
|
if (isTupleType(objectType)) {
|
|
error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2,
|
|
typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName));
|
|
}
|
|
else {
|
|
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
|
|
}
|
|
}
|
|
errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, IndexKind.Number));
|
|
return mapType(objectType, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType);
|
|
}
|
|
}
|
|
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) {
|
|
if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) {
|
|
return objectType;
|
|
}
|
|
const stringIndexInfo = getIndexInfoOfType(objectType, IndexKind.String);
|
|
const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || stringIndexInfo;
|
|
if (indexInfo) {
|
|
if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo === stringIndexInfo) {
|
|
if (accessExpression) {
|
|
error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType));
|
|
}
|
|
return undefined;
|
|
}
|
|
if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) {
|
|
const indexNode = getIndexNodeForAccessExpression(accessNode);
|
|
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType));
|
|
return indexInfo.type;
|
|
}
|
|
errorIfWritingToReadonlyIndex(indexInfo);
|
|
return indexInfo.type;
|
|
}
|
|
if (indexType.flags & TypeFlags.Never) {
|
|
return neverType;
|
|
}
|
|
if (isJSLiteralType(objectType)) {
|
|
return anyType;
|
|
}
|
|
if (accessExpression && !isConstEnumObjectType(objectType)) {
|
|
if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) {
|
|
error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
|
|
}
|
|
else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !suppressNoImplicitAnyError) {
|
|
if (propName !== undefined && typeHasStaticProperty(propName, objectType)) {
|
|
error(accessExpression, Diagnostics.Property_0_is_a_static_member_of_type_1, propName as string, typeToString(objectType));
|
|
}
|
|
else if (getIndexTypeOfType(objectType, IndexKind.Number)) {
|
|
error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number);
|
|
}
|
|
else {
|
|
let suggestion: string | undefined;
|
|
if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) {
|
|
if (suggestion !== undefined) {
|
|
error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion);
|
|
}
|
|
}
|
|
else {
|
|
const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType);
|
|
if (suggestion !== undefined) {
|
|
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion);
|
|
}
|
|
else {
|
|
let errorInfo: DiagnosticMessageChain | undefined;
|
|
if (indexType.flags & TypeFlags.EnumLiteral) {
|
|
errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType));
|
|
}
|
|
else if (indexType.flags & TypeFlags.UniqueESSymbol) {
|
|
const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression);
|
|
errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType));
|
|
}
|
|
else if (indexType.flags & TypeFlags.StringLiteral) {
|
|
errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType));
|
|
}
|
|
else if (indexType.flags & TypeFlags.NumberLiteral) {
|
|
errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType));
|
|
}
|
|
else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) {
|
|
errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType));
|
|
}
|
|
|
|
errorInfo = chainDiagnosticMessages(
|
|
errorInfo,
|
|
Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType)
|
|
);
|
|
diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
if (isJSLiteralType(objectType)) {
|
|
return anyType;
|
|
}
|
|
if (accessNode) {
|
|
const indexNode = getIndexNodeForAccessExpression(accessNode);
|
|
if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
|
|
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (<StringLiteralType | NumberLiteralType>indexType).value, typeToString(objectType));
|
|
}
|
|
else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) {
|
|
error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType));
|
|
}
|
|
else {
|
|
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType));
|
|
}
|
|
}
|
|
if (isTypeAny(indexType)) {
|
|
return indexType;
|
|
}
|
|
return undefined;
|
|
|
|
function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void {
|
|
if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) {
|
|
error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
|
|
}
|
|
}
|
|
}
|
|
|
|
function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) {
|
|
return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression :
|
|
accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType :
|
|
accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression :
|
|
accessNode;
|
|
}
|
|
|
|
function isGenericObjectType(type: Type): boolean {
|
|
if (type.flags & TypeFlags.UnionOrIntersection) {
|
|
if (!((<UnionOrIntersectionType>type).objectFlags & ObjectFlags.IsGenericObjectTypeComputed)) {
|
|
(<UnionOrIntersectionType>type).objectFlags |= ObjectFlags.IsGenericObjectTypeComputed |
|
|
(some((<UnionOrIntersectionType>type).types, isGenericObjectType) ? ObjectFlags.IsGenericObjectType : 0);
|
|
}
|
|
return !!((<UnionOrIntersectionType>type).objectFlags & ObjectFlags.IsGenericObjectType);
|
|
}
|
|
return !!(type.flags & TypeFlags.InstantiableNonPrimitive) || isGenericMappedType(type);
|
|
}
|
|
|
|
function isGenericIndexType(type: Type): boolean {
|
|
if (type.flags & TypeFlags.UnionOrIntersection) {
|
|
if (!((<UnionOrIntersectionType>type).objectFlags & ObjectFlags.IsGenericIndexTypeComputed)) {
|
|
(<UnionOrIntersectionType>type).objectFlags |= ObjectFlags.IsGenericIndexTypeComputed |
|
|
(some((<UnionOrIntersectionType>type).types, isGenericIndexType) ? ObjectFlags.IsGenericIndexType : 0);
|
|
}
|
|
return !!((<UnionOrIntersectionType>type).objectFlags & ObjectFlags.IsGenericIndexType);
|
|
}
|
|
return !!(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index));
|
|
}
|
|
|
|
function isThisTypeParameter(type: Type): boolean {
|
|
return !!(type.flags & TypeFlags.TypeParameter && (<TypeParameter>type).isThisType);
|
|
}
|
|
|
|
function getSimplifiedType(type: Type, writing: boolean): Type {
|
|
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type, writing) :
|
|
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(<ConditionalType>type, writing) :
|
|
type;
|
|
}
|
|
|
|
function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) {
|
|
// (T | U)[K] -> T[K] | U[K] (reading)
|
|
// (T | U)[K] -> T[K] & U[K] (writing)
|
|
// (T & U)[K] -> T[K] & U[K]
|
|
if (objectType.flags & TypeFlags.UnionOrIntersection) {
|
|
const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing));
|
|
return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types);
|
|
}
|
|
}
|
|
|
|
function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) {
|
|
// T[A | B] -> T[A] | T[B] (reading)
|
|
// T[A | B] -> T[A] & T[B] (writing)
|
|
if (indexType.flags & TypeFlags.Union) {
|
|
const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing));
|
|
return writing ? getIntersectionType(types) : getUnionType(types);
|
|
}
|
|
}
|
|
|
|
function unwrapSubstitution(type: Type): Type {
|
|
if (type.flags & TypeFlags.Substitution) {
|
|
return (type as SubstitutionType).substitute;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
|
|
// the type itself if no transformation is possible. The writing flag indicates that the type is
|
|
// the target of an assignment.
|
|
function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type {
|
|
const cache = writing ? "simplifiedForWriting" : "simplifiedForReading";
|
|
if (type[cache]) {
|
|
return type[cache] === circularConstraintType ? type : type[cache]!;
|
|
}
|
|
type[cache] = circularConstraintType;
|
|
// We recursively simplify the object type as it may in turn be an indexed access type. For example, with
|
|
// '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type.
|
|
const objectType = unwrapSubstitution(getSimplifiedType(type.objectType, writing));
|
|
const indexType = getSimplifiedType(type.indexType, writing);
|
|
// T[A | B] -> T[A] | T[B] (reading)
|
|
// T[A | B] -> T[A] & T[B] (writing)
|
|
const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing);
|
|
if (distributedOverIndex) {
|
|
return type[cache] = distributedOverIndex;
|
|
}
|
|
// Only do the inner distributions if the index can no longer be instantiated to cause index distribution again
|
|
if (!(indexType.flags & TypeFlags.Instantiable)) {
|
|
// (T | U)[K] -> T[K] | U[K] (reading)
|
|
// (T | U)[K] -> T[K] & U[K] (writing)
|
|
// (T & U)[K] -> T[K] & U[K]
|
|
const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing);
|
|
if (distributedOverObject) {
|
|
return type[cache] = distributedOverObject;
|
|
}
|
|
}
|
|
// So ultimately (reading):
|
|
// ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2]
|
|
|
|
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
|
|
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
|
|
// construct the type Box<T[X]>.
|
|
if (isGenericMappedType(objectType)) {
|
|
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
|
|
}
|
|
return type[cache] = type;
|
|
}
|
|
|
|
function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) {
|
|
const checkType = type.checkType;
|
|
const extendsType = type.extendsType;
|
|
const trueType = getTrueTypeFromConditionalType(type);
|
|
const falseType = getFalseTypeFromConditionalType(type);
|
|
// Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
|
|
if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) {
|
|
if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
|
|
return getSimplifiedType(trueType, writing);
|
|
}
|
|
else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
|
|
return neverType;
|
|
}
|
|
}
|
|
else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) {
|
|
if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
|
|
return neverType;
|
|
}
|
|
else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false
|
|
return getSimplifiedType(falseType, writing);
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Invokes union simplification logic to determine if an intersection is considered empty as a union constituent
|
|
*/
|
|
function isIntersectionEmpty(type1: Type, type2: Type) {
|
|
return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never);
|
|
}
|
|
|
|
function substituteIndexedMappedType(objectType: MappedType, index: Type) {
|
|
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]);
|
|
const templateMapper = combineTypeMappers(objectType.mapper, mapper);
|
|
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
|
|
}
|
|
|
|
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression): Type {
|
|
return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, AccessFlags.None) || (accessNode ? errorType : unknownType);
|
|
}
|
|
|
|
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None): Type | undefined {
|
|
if (objectType === wildcardType || indexType === wildcardType) {
|
|
return wildcardType;
|
|
}
|
|
// If the object type has a string index signature and no other members we know that the result will
|
|
// always be the type of that index signature and we can simplify accordingly.
|
|
if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) {
|
|
indexType = stringType;
|
|
}
|
|
// If the index type is generic, or if the object type is generic and doesn't originate in an expression,
|
|
// we are performing a higher-order index access where we cannot meaningfully access the properties of the
|
|
// object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in
|
|
// an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]'
|
|
// has always been resolved eagerly using the constraint type of 'this' at the given location.
|
|
if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType) && isGenericObjectType(objectType)) {
|
|
if (objectType.flags & TypeFlags.AnyOrUnknown) {
|
|
return objectType;
|
|
}
|
|
// Defer the operation by creating an indexed access type.
|
|
const id = objectType.id + "," + indexType.id;
|
|
let type = indexedAccessTypes.get(id);
|
|
if (!type) {
|
|
indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType));
|
|
}
|
|
return type;
|
|
}
|
|
// In the following we resolve T[K] to the type of the property in T selected by K.
|
|
// We treat boolean as different from other unions to improve errors;
|
|
// skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'.
|
|
const apparentObjectType = getApparentType(getReducedType(objectType));
|
|
if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) {
|
|
const propTypes: Type[] = [];
|
|
let wasMissingProp = false;
|
|
for (const t of (<UnionType>indexType).types) {
|
|
const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags);
|
|
if (propType) {
|
|
propTypes.push(propType);
|
|
}
|
|
else if (!accessNode) {
|
|
// If there's no error node, we can immeditely stop, since error reporting is off
|
|
return undefined;
|
|
}
|
|
else {
|
|
// Otherwise we set a flag and return at the end of the loop so we still mark all errors
|
|
wasMissingProp = true;
|
|
}
|
|
}
|
|
if (wasMissingProp) {
|
|
return undefined;
|
|
}
|
|
return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes);
|
|
}
|
|
return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol);
|
|
}
|
|
|
|
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
const objectType = getTypeFromTypeNode(node.objectType);
|
|
const indexType = getTypeFromTypeNode(node.indexType);
|
|
const resolved = getIndexedAccessType(objectType, indexType, node);
|
|
links.resolvedType = resolved.flags & TypeFlags.IndexedAccess &&
|
|
(<IndexedAccessType>resolved).objectType === objectType &&
|
|
(<IndexedAccessType>resolved).indexType === indexType ?
|
|
getConstrainedTypeVariable(<IndexedAccessType>resolved, node) : resolved;
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function getTypeFromMappedTypeNode(node: MappedTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
const type = <MappedType>createObjectType(ObjectFlags.Mapped, node.symbol);
|
|
type.declaration = node;
|
|
type.aliasSymbol = getAliasSymbolForTypeNode(node);
|
|
type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol);
|
|
links.resolvedType = type;
|
|
// Eagerly resolve the constraint type which forces an error if the constraint type circularly
|
|
// references itself through one or more type aliases.
|
|
getConstraintTypeFromMappedType(type);
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function getActualTypeVariable(type: Type): Type {
|
|
if (type.flags & TypeFlags.Substitution) {
|
|
return (<SubstitutionType>type).typeVariable;
|
|
}
|
|
if (type.flags & TypeFlags.IndexedAccess && (
|
|
(<IndexedAccessType>type).objectType.flags & TypeFlags.Substitution ||
|
|
(<IndexedAccessType>type).indexType.flags & TypeFlags.Substitution)) {
|
|
return getIndexedAccessType(getActualTypeVariable((<IndexedAccessType>type).objectType), getActualTypeVariable((<IndexedAccessType>type).indexType));
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): Type {
|
|
const checkType = instantiateType(root.checkType, mapper);
|
|
const extendsType = instantiateType(root.extendsType, mapper);
|
|
if (checkType === wildcardType || extendsType === wildcardType) {
|
|
return wildcardType;
|
|
}
|
|
const checkTypeInstantiable = isGenericObjectType(checkType) || isGenericIndexType(checkType);
|
|
let combinedMapper: TypeMapper | undefined;
|
|
if (root.inferTypeParameters) {
|
|
const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
|
|
// We skip inference of the possible `infer` types unles the `extendsType` _is_ an infer type
|
|
// if it was, it's trivial to say that extendsType = checkType, however such a pattern is used to
|
|
// "reset" the type being build up during constraint calculation and avoid making an apparently "infinite" constraint
|
|
// so in those cases we refain from performing inference and retain the uninfered type parameter
|
|
if (!checkTypeInstantiable || !some(root.inferTypeParameters, t => t === extendsType)) {
|
|
// We don't want inferences from constraints as they may cause us to eagerly resolve the
|
|
// conditional type instead of deferring resolution. Also, we always want strict function
|
|
// types rules (i.e. proper contravariance) for inferences.
|
|
inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
|
|
}
|
|
combinedMapper = mergeTypeMappers(mapper, context.mapper);
|
|
}
|
|
// Instantiate the extends type including inferences for 'infer T' type parameters
|
|
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
|
|
// We attempt to resolve the conditional type only when the check and extends types are non-generic
|
|
if (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType)) {
|
|
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
|
|
return instantiateType(root.trueType, combinedMapper || mapper);
|
|
}
|
|
// Return union of trueType and falseType for 'any' since it matches anything
|
|
if (checkType.flags & TypeFlags.Any) {
|
|
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
|
|
}
|
|
// Return falseType for a definitely false extends check. We check an instantiations of the two
|
|
// types with type parameters mapped to the wildcard type, the most permissive instantiations
|
|
// possible (the wildcard type is assignable to and from all types). If those are not related,
|
|
// then no instantiations will be and we can just return the false branch type.
|
|
if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) {
|
|
return instantiateType(root.falseType, mapper);
|
|
}
|
|
// Return trueType for a definitely true extends check. We check instantiations of the two
|
|
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
|
|
// that has no constraint. This ensures that, for example, the type
|
|
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
|
|
// doesn't immediately resolve to 'string' instead of being deferred.
|
|
if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
|
|
return instantiateType(root.trueType, combinedMapper || mapper);
|
|
}
|
|
}
|
|
// Return a deferred type for a check that is neither definitely true nor definitely false
|
|
const erasedCheckType = getActualTypeVariable(checkType);
|
|
const result = <ConditionalType>createType(TypeFlags.Conditional);
|
|
result.root = root;
|
|
result.checkType = erasedCheckType;
|
|
result.extendsType = extendsType;
|
|
result.mapper = mapper;
|
|
result.combinedMapper = combinedMapper;
|
|
result.aliasSymbol = root.aliasSymbol;
|
|
result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
|
|
return result;
|
|
}
|
|
|
|
function getTrueTypeFromConditionalType(type: ConditionalType) {
|
|
return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper));
|
|
}
|
|
|
|
function getFalseTypeFromConditionalType(type: ConditionalType) {
|
|
return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper));
|
|
}
|
|
|
|
function getInferredTrueTypeFromConditionalType(type: ConditionalType) {
|
|
return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(type.root.trueType, type.combinedMapper) : getTrueTypeFromConditionalType(type));
|
|
}
|
|
|
|
function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined {
|
|
let result: TypeParameter[] | undefined;
|
|
if (node.locals) {
|
|
node.locals.forEach(symbol => {
|
|
if (symbol.flags & SymbolFlags.TypeParameter) {
|
|
result = append(result, getDeclaredTypeOfSymbol(symbol));
|
|
}
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
const checkType = getTypeFromTypeNode(node.checkType);
|
|
const aliasSymbol = getAliasSymbolForTypeNode(node);
|
|
const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol);
|
|
const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true);
|
|
const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node));
|
|
const root: ConditionalRoot = {
|
|
node,
|
|
checkType,
|
|
extendsType: getTypeFromTypeNode(node.extendsType),
|
|
trueType: getTypeFromTypeNode(node.trueType),
|
|
falseType: getTypeFromTypeNode(node.falseType),
|
|
isDistributive: !!(checkType.flags & TypeFlags.TypeParameter),
|
|
inferTypeParameters: getInferTypeParameters(node),
|
|
outerTypeParameters,
|
|
instantiations: undefined,
|
|
aliasSymbol,
|
|
aliasTypeArguments
|
|
};
|
|
links.resolvedType = getConditionalType(root, /*mapper*/ undefined);
|
|
if (outerTypeParameters) {
|
|
root.instantiations = createMap<Type>();
|
|
root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType);
|
|
}
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function getTypeFromInferTypeNode(node: InferTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter));
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function getIdentifierChain(node: EntityName): Identifier[] {
|
|
if (isIdentifier(node)) {
|
|
return [node];
|
|
}
|
|
else {
|
|
return append(getIdentifierChain(node.left), node.right);
|
|
}
|
|
}
|
|
|
|
function getTypeFromImportTypeNode(node: ImportTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
if (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments
|
|
error(node, Diagnostics.Type_arguments_cannot_be_used_here);
|
|
links.resolvedSymbol = unknownSymbol;
|
|
return links.resolvedType = errorType;
|
|
}
|
|
if (!isLiteralImportTypeNode(node)) {
|
|
error(node.argument, Diagnostics.String_literal_expected);
|
|
links.resolvedSymbol = unknownSymbol;
|
|
return links.resolvedType = errorType;
|
|
}
|
|
const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type;
|
|
// TODO: Future work: support unions/generics/whatever via a deferred import-type
|
|
const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal);
|
|
if (!innerModuleSymbol) {
|
|
links.resolvedSymbol = unknownSymbol;
|
|
return links.resolvedType = errorType;
|
|
}
|
|
const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false);
|
|
if (!nodeIsMissing(node.qualifier)) {
|
|
const nameStack: Identifier[] = getIdentifierChain(node.qualifier!);
|
|
let currentNamespace = moduleSymbol;
|
|
let current: Identifier | undefined;
|
|
while (current = nameStack.shift()) {
|
|
const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning;
|
|
const next = getSymbol(getExportsOfSymbol(getMergedSymbol(resolveSymbol(currentNamespace))), current.escapedText, meaning);
|
|
if (!next) {
|
|
error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current));
|
|
return links.resolvedType = errorType;
|
|
}
|
|
getNodeLinks(current).resolvedSymbol = next;
|
|
getNodeLinks(current.parent).resolvedSymbol = next;
|
|
currentNamespace = next;
|
|
}
|
|
links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning);
|
|
}
|
|
else {
|
|
if (moduleSymbol.flags & targetMeaning) {
|
|
links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning);
|
|
}
|
|
else {
|
|
const errorMessage = targetMeaning === SymbolFlags.Value
|
|
? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here
|
|
: Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0;
|
|
|
|
error(node, errorMessage, node.argument.literal.text);
|
|
|
|
links.resolvedSymbol = unknownSymbol;
|
|
links.resolvedType = errorType;
|
|
}
|
|
}
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) {
|
|
const resolvedSymbol = resolveSymbol(symbol);
|
|
links.resolvedSymbol = resolvedSymbol;
|
|
if (meaning === SymbolFlags.Value) {
|
|
return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias
|
|
}
|
|
else {
|
|
return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol
|
|
}
|
|
}
|
|
|
|
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
// Deferred resolution of members is handled by resolveObjectTypeMembers
|
|
const aliasSymbol = getAliasSymbolForTypeNode(node);
|
|
if (getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) {
|
|
links.resolvedType = emptyTypeLiteralType;
|
|
}
|
|
else {
|
|
let type = createObjectType(ObjectFlags.Anonymous, node.symbol);
|
|
type.aliasSymbol = aliasSymbol;
|
|
type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol);
|
|
if (isJSDocTypeLiteral(node) && node.isArrayType) {
|
|
type = createArrayType(type);
|
|
}
|
|
links.resolvedType = type;
|
|
}
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function getAliasSymbolForTypeNode(node: Node) {
|
|
let host = node.parent;
|
|
while (isParenthesizedTypeNode(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) {
|
|
host = host.parent;
|
|
}
|
|
return isTypeAlias(host) ? getSymbolOfNode(host) : undefined;
|
|
}
|
|
|
|
function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) {
|
|
return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined;
|
|
}
|
|
|
|
function isNonGenericObjectType(type: Type) {
|
|
return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type);
|
|
}
|
|
|
|
function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) {
|
|
return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index));
|
|
}
|
|
|
|
function isSinglePropertyAnonymousObjectType(type: Type) {
|
|
return !!(type.flags & TypeFlags.Object) &&
|
|
!!(getObjectFlags(type) & ObjectFlags.Anonymous) &&
|
|
(length(getPropertiesOfType(type)) === 1 || every(getPropertiesOfType(type), p => !!(p.flags & SymbolFlags.Optional)));
|
|
}
|
|
|
|
function tryMergeUnionOfObjectTypeAndEmptyObject(type: UnionType, readonly: boolean): Type | undefined {
|
|
if (type.types.length === 2) {
|
|
const firstType = type.types[0];
|
|
const secondType = type.types[1];
|
|
if (every(type.types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) {
|
|
return isEmptyObjectType(firstType) ? firstType : isEmptyObjectType(secondType) ? secondType : emptyObjectType;
|
|
}
|
|
if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(firstType) && isSinglePropertyAnonymousObjectType(secondType)) {
|
|
return getAnonymousPartialType(secondType);
|
|
}
|
|
if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(secondType) && isSinglePropertyAnonymousObjectType(firstType)) {
|
|
return getAnonymousPartialType(firstType);
|
|
}
|
|
}
|
|
|
|
function getAnonymousPartialType(type: Type) {
|
|
// gets the type as if it had been spread, but where everything in the spread is made optional
|
|
const members = createSymbolTable();
|
|
for (const prop of getPropertiesOfType(type)) {
|
|
if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) {
|
|
// do nothing, skip privates
|
|
}
|
|
else if (isSpreadableProperty(prop)) {
|
|
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
|
|
const flags = SymbolFlags.Property | SymbolFlags.Optional;
|
|
const result = createSymbol(flags, prop.escapedName, readonly ? CheckFlags.Readonly : 0);
|
|
result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop);
|
|
result.declarations = prop.declarations;
|
|
result.nameType = getSymbolLinks(prop).nameType;
|
|
result.syntheticOrigin = prop;
|
|
members.set(prop.escapedName, result);
|
|
}
|
|
}
|
|
const spread = createAnonymousType(
|
|
type.symbol,
|
|
members,
|
|
emptyArray,
|
|
emptyArray,
|
|
getIndexInfoOfType(type, IndexKind.String),
|
|
getIndexInfoOfType(type, IndexKind.Number));
|
|
spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
|
|
return spread;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Since the source of spread types are object literals, which are not binary,
|
|
* this function should be called in a left folding style, with left = previous result of getSpreadType
|
|
* and right = the new element to be spread.
|
|
*/
|
|
function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type {
|
|
if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) {
|
|
return anyType;
|
|
}
|
|
if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) {
|
|
return unknownType;
|
|
}
|
|
if (left.flags & TypeFlags.Never) {
|
|
return right;
|
|
}
|
|
if (right.flags & TypeFlags.Never) {
|
|
return left;
|
|
}
|
|
if (left.flags & TypeFlags.Union) {
|
|
const merged = tryMergeUnionOfObjectTypeAndEmptyObject(left as UnionType, readonly);
|
|
if (merged) {
|
|
return getSpreadType(merged, right, symbol, objectFlags, readonly);
|
|
}
|
|
return mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly));
|
|
}
|
|
if (right.flags & TypeFlags.Union) {
|
|
const merged = tryMergeUnionOfObjectTypeAndEmptyObject(right as UnionType, readonly);
|
|
if (merged) {
|
|
return getSpreadType(left, merged, symbol, objectFlags, readonly);
|
|
}
|
|
return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly));
|
|
}
|
|
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) {
|
|
return left;
|
|
}
|
|
|
|
if (isGenericObjectType(left) || isGenericObjectType(right)) {
|
|
if (isEmptyObjectType(left)) {
|
|
return right;
|
|
}
|
|
// When the left type is an intersection, we may need to merge the last constituent of the
|
|
// intersection with the right type. For example when the left type is 'T & { a: string }'
|
|
// and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'.
|
|
if (left.flags & TypeFlags.Intersection) {
|
|
const types = (<IntersectionType>left).types;
|
|
const lastLeft = types[types.length - 1];
|
|
if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) {
|
|
return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)]));
|
|
}
|
|
}
|
|
return getIntersectionType([left, right]);
|
|
}
|
|
|
|
const members = createSymbolTable();
|
|
const skippedPrivateMembers = createUnderscoreEscapedMap<boolean>();
|
|
let stringIndexInfo: IndexInfo | undefined;
|
|
let numberIndexInfo: IndexInfo | undefined;
|
|
if (left === emptyObjectType) {
|
|
// for the first spread element, left === emptyObjectType, so take the right's string indexer
|
|
stringIndexInfo = getIndexInfoOfType(right, IndexKind.String);
|
|
numberIndexInfo = getIndexInfoOfType(right, IndexKind.Number);
|
|
}
|
|
else {
|
|
stringIndexInfo = unionSpreadIndexInfos(getIndexInfoOfType(left, IndexKind.String), getIndexInfoOfType(right, IndexKind.String));
|
|
numberIndexInfo = unionSpreadIndexInfos(getIndexInfoOfType(left, IndexKind.Number), getIndexInfoOfType(right, IndexKind.Number));
|
|
}
|
|
|
|
for (const rightProp of getPropertiesOfType(right)) {
|
|
if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) {
|
|
skippedPrivateMembers.set(rightProp.escapedName, true);
|
|
}
|
|
else if (isSpreadableProperty(rightProp)) {
|
|
members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly));
|
|
}
|
|
}
|
|
|
|
for (const leftProp of getPropertiesOfType(left)) {
|
|
if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) {
|
|
continue;
|
|
}
|
|
if (members.has(leftProp.escapedName)) {
|
|
const rightProp = members.get(leftProp.escapedName)!;
|
|
const rightType = getTypeOfSymbol(rightProp);
|
|
if (rightProp.flags & SymbolFlags.Optional) {
|
|
const declarations = concatenate(leftProp.declarations, rightProp.declarations);
|
|
const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional);
|
|
const result = createSymbol(flags, leftProp.escapedName);
|
|
result.type = getUnionType([getTypeOfSymbol(leftProp), getTypeWithFacts(rightType, TypeFacts.NEUndefined)]);
|
|
result.leftSpread = leftProp;
|
|
result.rightSpread = rightProp;
|
|
result.declarations = declarations;
|
|
result.nameType = getSymbolLinks(leftProp).nameType;
|
|
members.set(leftProp.escapedName, result);
|
|
}
|
|
}
|
|
else {
|
|
members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly));
|
|
}
|
|
}
|
|
|
|
const spread = createAnonymousType(
|
|
symbol,
|
|
members,
|
|
emptyArray,
|
|
emptyArray,
|
|
getIndexInfoWithReadonly(stringIndexInfo, readonly),
|
|
getIndexInfoWithReadonly(numberIndexInfo, readonly));
|
|
spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags;
|
|
return spread;
|
|
}
|
|
|
|
/** We approximate own properties as non-methods plus methods that are inside the object literal */
|
|
function isSpreadableProperty(prop: Symbol): boolean {
|
|
return !some(prop.declarations, isPrivateIdentifierPropertyDeclaration) &&
|
|
(!(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) ||
|
|
!prop.declarations.some(decl => isClassLike(decl.parent)));
|
|
}
|
|
|
|
function getSpreadSymbol(prop: Symbol, readonly: boolean) {
|
|
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
|
|
if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) {
|
|
return prop;
|
|
}
|
|
const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional);
|
|
const result = createSymbol(flags, prop.escapedName, readonly ? CheckFlags.Readonly : 0);
|
|
result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop);
|
|
result.declarations = prop.declarations;
|
|
result.nameType = getSymbolLinks(prop).nameType;
|
|
result.syntheticOrigin = prop;
|
|
return result;
|
|
}
|
|
|
|
function getIndexInfoWithReadonly(info: IndexInfo | undefined, readonly: boolean) {
|
|
return info && info.isReadonly !== readonly ? createIndexInfo(info.type, readonly, info.declaration) : info;
|
|
}
|
|
|
|
function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol: Symbol | undefined) {
|
|
const type = <LiteralType>createType(flags);
|
|
type.symbol = symbol!;
|
|
type.value = value;
|
|
return type;
|
|
}
|
|
|
|
function getFreshTypeOfLiteralType(type: Type): Type {
|
|
if (type.flags & TypeFlags.Literal) {
|
|
if (!(<LiteralType>type).freshType) {
|
|
const freshType = createLiteralType(type.flags, (<LiteralType>type).value, (<LiteralType>type).symbol);
|
|
freshType.regularType = <LiteralType>type;
|
|
freshType.freshType = freshType;
|
|
(<LiteralType>type).freshType = freshType;
|
|
}
|
|
return (<LiteralType>type).freshType;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getRegularTypeOfLiteralType(type: Type): Type {
|
|
return type.flags & TypeFlags.Literal ? (<LiteralType>type).regularType :
|
|
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getRegularTypeOfLiteralType)) :
|
|
type;
|
|
}
|
|
|
|
function isFreshLiteralType(type: Type) {
|
|
return !!(type.flags & TypeFlags.Literal) && (<LiteralType>type).freshType === type;
|
|
}
|
|
|
|
function getLiteralType(value: string | number | PseudoBigInt, enumId?: number, symbol?: Symbol) {
|
|
// We store all literal types in a single map with keys of the form '#NNN' and '@SSS',
|
|
// where NNN is the text representation of a numeric literal and SSS are the characters
|
|
// of a string literal. For literal enum members we use 'EEE#NNN' and 'EEE@SSS', where
|
|
// EEE is a unique id for the containing enum type.
|
|
const qualifier = typeof value === "number" ? "#" : typeof value === "string" ? "@" : "n";
|
|
const key = (enumId ? enumId : "") + qualifier + (typeof value === "object" ? pseudoBigIntToString(value) : value);
|
|
let type = literalTypes.get(key);
|
|
if (!type) {
|
|
const flags = (typeof value === "number" ? TypeFlags.NumberLiteral :
|
|
typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.BigIntLiteral) |
|
|
(enumId ? TypeFlags.EnumLiteral : 0);
|
|
literalTypes.set(key, type = createLiteralType(flags, value, symbol));
|
|
type.regularType = type;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal));
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function createUniqueESSymbolType(symbol: Symbol) {
|
|
const type = <UniqueESSymbolType>createType(TypeFlags.UniqueESSymbol);
|
|
type.symbol = symbol;
|
|
type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String;
|
|
return type;
|
|
}
|
|
|
|
function getESSymbolLikeTypeForNode(node: Node) {
|
|
if (isValidESSymbolDeclaration(node)) {
|
|
const symbol = getSymbolOfNode(node);
|
|
const links = getSymbolLinks(symbol);
|
|
return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol));
|
|
}
|
|
return esSymbolType;
|
|
}
|
|
|
|
function getThisType(node: Node): Type {
|
|
const container = getThisContainer(node, /*includeArrowFunctions*/ false);
|
|
const parent = container && container.parent;
|
|
if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) {
|
|
if (!hasModifier(container, ModifierFlags.Static) &&
|
|
(!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))) {
|
|
return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!;
|
|
}
|
|
}
|
|
|
|
// inside x.prototype = { ... }
|
|
if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) {
|
|
return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!;
|
|
}
|
|
// /** @return {this} */
|
|
// x.prototype.m = function() { ... }
|
|
const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined;
|
|
if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) {
|
|
return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!;
|
|
}
|
|
// inside constructor function C() { ... }
|
|
if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) {
|
|
return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(container)).thisType!;
|
|
}
|
|
error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface);
|
|
return errorType;
|
|
}
|
|
|
|
function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
links.resolvedType = getThisType(node);
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function getTypeFromTypeNode(node: TypeNode): Type {
|
|
switch (node.kind) {
|
|
case SyntaxKind.AnyKeyword:
|
|
case SyntaxKind.JSDocAllType:
|
|
case SyntaxKind.JSDocUnknownType:
|
|
return anyType;
|
|
case SyntaxKind.UnknownKeyword:
|
|
return unknownType;
|
|
case SyntaxKind.StringKeyword:
|
|
return stringType;
|
|
case SyntaxKind.NumberKeyword:
|
|
return numberType;
|
|
case SyntaxKind.BigIntKeyword:
|
|
return bigintType;
|
|
case SyntaxKind.BooleanKeyword:
|
|
return booleanType;
|
|
case SyntaxKind.SymbolKeyword:
|
|
return esSymbolType;
|
|
case SyntaxKind.VoidKeyword:
|
|
return voidType;
|
|
case SyntaxKind.UndefinedKeyword:
|
|
return undefinedType;
|
|
case SyntaxKind.NullKeyword:
|
|
return nullType;
|
|
case SyntaxKind.NeverKeyword:
|
|
return neverType;
|
|
case SyntaxKind.ObjectKeyword:
|
|
return node.flags & NodeFlags.JavaScriptFile && !noImplicitAny ? anyType : nonPrimitiveType;
|
|
case SyntaxKind.ThisType:
|
|
case SyntaxKind.ThisKeyword:
|
|
return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode);
|
|
case SyntaxKind.LiteralType:
|
|
return getTypeFromLiteralTypeNode(<LiteralTypeNode>node);
|
|
case SyntaxKind.TypeReference:
|
|
return getTypeFromTypeReference(<TypeReferenceNode>node);
|
|
case SyntaxKind.TypePredicate:
|
|
return (<TypePredicateNode>node).assertsModifier ? voidType : booleanType;
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
return getTypeFromTypeReference(<ExpressionWithTypeArguments>node);
|
|
case SyntaxKind.TypeQuery:
|
|
return getTypeFromTypeQueryNode(<TypeQueryNode>node);
|
|
case SyntaxKind.ArrayType:
|
|
case SyntaxKind.TupleType:
|
|
return getTypeFromArrayOrTupleTypeNode(<ArrayTypeNode | TupleTypeNode>node);
|
|
case SyntaxKind.OptionalType:
|
|
return getTypeFromOptionalTypeNode(<OptionalTypeNode>node);
|
|
case SyntaxKind.UnionType:
|
|
return getTypeFromUnionTypeNode(<UnionTypeNode>node);
|
|
case SyntaxKind.IntersectionType:
|
|
return getTypeFromIntersectionTypeNode(<IntersectionTypeNode>node);
|
|
case SyntaxKind.JSDocNullableType:
|
|
return getTypeFromJSDocNullableTypeNode(<JSDocNullableType>node);
|
|
case SyntaxKind.JSDocOptionalType:
|
|
return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type));
|
|
case SyntaxKind.ParenthesizedType:
|
|
case SyntaxKind.JSDocNonNullableType:
|
|
case SyntaxKind.JSDocTypeExpression:
|
|
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression>node).type);
|
|
case SyntaxKind.RestType:
|
|
return getElementTypeOfArrayType(getTypeFromTypeNode((<RestTypeNode>node).type)) || errorType;
|
|
case SyntaxKind.JSDocVariadicType:
|
|
return getTypeFromJSDocVariadicType(node as JSDocVariadicType);
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
case SyntaxKind.TypeLiteral:
|
|
case SyntaxKind.JSDocTypeLiteral:
|
|
case SyntaxKind.JSDocFunctionType:
|
|
case SyntaxKind.JSDocSignature:
|
|
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node);
|
|
case SyntaxKind.TypeOperator:
|
|
return getTypeFromTypeOperatorNode(<TypeOperatorNode>node);
|
|
case SyntaxKind.IndexedAccessType:
|
|
return getTypeFromIndexedAccessTypeNode(<IndexedAccessTypeNode>node);
|
|
case SyntaxKind.MappedType:
|
|
return getTypeFromMappedTypeNode(<MappedTypeNode>node);
|
|
case SyntaxKind.ConditionalType:
|
|
return getTypeFromConditionalTypeNode(<ConditionalTypeNode>node);
|
|
case SyntaxKind.InferType:
|
|
return getTypeFromInferTypeNode(<InferTypeNode>node);
|
|
case SyntaxKind.ImportType:
|
|
return getTypeFromImportTypeNode(<ImportTypeNode>node);
|
|
// This function assumes that an identifier or qualified name is a type expression
|
|
// Callers should first ensure this by calling isTypeNode
|
|
case SyntaxKind.Identifier:
|
|
case SyntaxKind.QualifiedName:
|
|
const symbol = getSymbolAtLocation(node);
|
|
return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType;
|
|
default:
|
|
return errorType;
|
|
}
|
|
}
|
|
|
|
function instantiateList<T>(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[];
|
|
function instantiateList<T>(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined;
|
|
function instantiateList<T>(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined {
|
|
if (items && items.length) {
|
|
for (let i = 0; i < items.length; i++) {
|
|
const item = items[i];
|
|
const mapped = instantiator(item, mapper);
|
|
if (item !== mapped) {
|
|
const result = i === 0 ? [] : items.slice(0, i);
|
|
result.push(mapped);
|
|
for (i++; i < items.length; i++) {
|
|
result.push(instantiator(items[i], mapper));
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
function instantiateTypes(types: readonly Type[], mapper: TypeMapper): readonly Type[];
|
|
function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined;
|
|
function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined {
|
|
return instantiateList<Type>(types, mapper, instantiateType);
|
|
}
|
|
|
|
function instantiateSignatures(signatures: readonly Signature[], mapper: TypeMapper): readonly Signature[] {
|
|
return instantiateList<Signature>(signatures, mapper, instantiateSignature);
|
|
}
|
|
|
|
function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper {
|
|
return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : makeArrayTypeMapper(sources, targets);
|
|
}
|
|
|
|
function getMappedType(type: Type, mapper: TypeMapper): Type {
|
|
switch (mapper.kind) {
|
|
case TypeMapKind.Simple:
|
|
return type === mapper.source ? mapper.target : type;
|
|
case TypeMapKind.Array:
|
|
const sources = mapper.sources;
|
|
const targets = mapper.targets;
|
|
for (let i = 0; i < sources.length; i++) {
|
|
if (type === sources[i]) {
|
|
return targets ? targets[i] : anyType;
|
|
}
|
|
}
|
|
return type;
|
|
case TypeMapKind.Function:
|
|
return mapper.func(type);
|
|
case TypeMapKind.Composite:
|
|
case TypeMapKind.Merged:
|
|
const t1 = getMappedType(type, mapper.mapper1);
|
|
return t1 !== type && mapper.kind === TypeMapKind.Composite ? instantiateType(t1, mapper.mapper2) : getMappedType(t1, mapper.mapper2);
|
|
}
|
|
}
|
|
|
|
function makeUnaryTypeMapper(source: Type, target: Type): TypeMapper {
|
|
return { kind: TypeMapKind.Simple, source, target };
|
|
}
|
|
|
|
function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper {
|
|
return { kind: TypeMapKind.Array, sources, targets };
|
|
}
|
|
|
|
function makeFunctionTypeMapper(func: (t: Type) => Type): TypeMapper {
|
|
return { kind: TypeMapKind.Function, func };
|
|
}
|
|
|
|
function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper {
|
|
return { kind, mapper1, mapper2 };
|
|
}
|
|
|
|
function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper {
|
|
return createTypeMapper(sources, /*targets*/ undefined);
|
|
}
|
|
|
|
/**
|
|
* Maps forward-references to later types parameters to the empty object type.
|
|
* This is used during inference when instantiating type parameter defaults.
|
|
*/
|
|
function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper {
|
|
return makeFunctionTypeMapper(t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t);
|
|
}
|
|
|
|
function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper {
|
|
return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2;
|
|
}
|
|
|
|
function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper {
|
|
return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2;
|
|
}
|
|
|
|
function prependTypeMapping(source: Type, target: Type, mapper: TypeMapper | undefined) {
|
|
return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, makeUnaryTypeMapper(source, target), mapper);
|
|
}
|
|
|
|
function appendTypeMapping(mapper: TypeMapper | undefined, source: Type, target: Type) {
|
|
return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, mapper, makeUnaryTypeMapper(source, target));
|
|
}
|
|
|
|
function getRestrictiveTypeParameter(tp: TypeParameter) {
|
|
return tp.constraint === unknownType ? tp : tp.restrictiveInstantiation || (
|
|
tp.restrictiveInstantiation = createTypeParameter(tp.symbol),
|
|
(tp.restrictiveInstantiation as TypeParameter).constraint = unknownType,
|
|
tp.restrictiveInstantiation
|
|
);
|
|
}
|
|
|
|
function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter {
|
|
const result = createTypeParameter(typeParameter.symbol);
|
|
result.target = typeParameter;
|
|
return result;
|
|
}
|
|
|
|
function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate {
|
|
return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper));
|
|
}
|
|
|
|
function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature {
|
|
let freshTypeParameters: TypeParameter[] | undefined;
|
|
if (signature.typeParameters && !eraseTypeParameters) {
|
|
// First create a fresh set of type parameters, then include a mapping from the old to the
|
|
// new type parameters in the mapper function. Finally store this mapper in the new type
|
|
// parameters such that we can use it when instantiating constraints.
|
|
freshTypeParameters = map(signature.typeParameters, cloneTypeParameter);
|
|
mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper);
|
|
for (const tp of freshTypeParameters) {
|
|
tp.mapper = mapper;
|
|
}
|
|
}
|
|
// Don't compute resolvedReturnType and resolvedTypePredicate now,
|
|
// because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.)
|
|
// See GH#17600.
|
|
const result = createSignature(signature.declaration, freshTypeParameters,
|
|
signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper),
|
|
instantiateList(signature.parameters, mapper, instantiateSymbol),
|
|
/*resolvedReturnType*/ undefined,
|
|
/*resolvedTypePredicate*/ undefined,
|
|
signature.minArgumentCount,
|
|
signature.flags & SignatureFlags.PropagatingFlags);
|
|
result.target = signature;
|
|
result.mapper = mapper;
|
|
return result;
|
|
}
|
|
|
|
function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol {
|
|
const links = getSymbolLinks(symbol);
|
|
if (links.type && !couldContainTypeVariables(links.type)) {
|
|
// If the type of the symbol is already resolved, and if that type could not possibly
|
|
// be affected by instantiation, simply return the symbol itself.
|
|
return symbol;
|
|
}
|
|
if (getCheckFlags(symbol) & CheckFlags.Instantiated) {
|
|
// If symbol being instantiated is itself a instantiation, fetch the original target and combine the
|
|
// type mappers. This ensures that original type identities are properly preserved and that aliases
|
|
// always reference a non-aliases.
|
|
symbol = links.target!;
|
|
mapper = combineTypeMappers(links.mapper, mapper);
|
|
}
|
|
// Keep the flags from the symbol we're instantiating. Mark that is instantiated, and
|
|
// also transient so that we can just store data on it directly.
|
|
const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter));
|
|
result.declarations = symbol.declarations;
|
|
result.parent = symbol.parent;
|
|
result.target = symbol;
|
|
result.mapper = mapper;
|
|
if (symbol.valueDeclaration) {
|
|
result.valueDeclaration = symbol.valueDeclaration;
|
|
}
|
|
if (links.nameType) {
|
|
result.nameType = links.nameType;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper) {
|
|
const target = type.objectFlags & ObjectFlags.Instantiated ? type.target! : type;
|
|
const node = type.objectFlags & ObjectFlags.Reference ? (<TypeReference>type).node! : type.symbol.declarations[0];
|
|
const links = getNodeLinks(node);
|
|
let typeParameters = links.outerTypeParameters;
|
|
if (!typeParameters) {
|
|
// The first time an anonymous type is instantiated we compute and store a list of the type
|
|
// parameters that are in scope (and therefore potentially referenced). For type literals that
|
|
// aren't the right hand side of a generic type alias declaration we optimize by reducing the
|
|
// set of type parameters to those that are possibly referenced in the literal.
|
|
let declaration = node;
|
|
if (isInJSFile(declaration)) {
|
|
const paramTag = findAncestor(declaration, isJSDocParameterTag);
|
|
if (paramTag) {
|
|
const paramSymbol = getParameterSymbolFromJSDoc(paramTag);
|
|
if (paramSymbol) {
|
|
declaration = paramSymbol.valueDeclaration;
|
|
}
|
|
}
|
|
}
|
|
let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true);
|
|
if (isJSConstructor(declaration)) {
|
|
const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters);
|
|
outerTypeParameters = addRange(outerTypeParameters, templateTagParameters);
|
|
}
|
|
typeParameters = outerTypeParameters || emptyArray;
|
|
typeParameters = (target.objectFlags & ObjectFlags.Reference || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ?
|
|
filter(typeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) :
|
|
typeParameters;
|
|
links.outerTypeParameters = typeParameters;
|
|
if (typeParameters.length) {
|
|
links.instantiations = createMap<Type>();
|
|
links.instantiations.set(getTypeListId(typeParameters), target);
|
|
}
|
|
}
|
|
if (typeParameters.length) {
|
|
// We are instantiating an anonymous type that has one or more type parameters in scope. Apply the
|
|
// mapper to the type parameters to produce the effective list of type arguments, and compute the
|
|
// instantiation cache key from the type IDs of the type arguments.
|
|
const combinedMapper = combineTypeMappers(type.mapper, mapper);
|
|
const typeArguments = map(typeParameters, t => getMappedType(t, combinedMapper));
|
|
const id = getTypeListId(typeArguments);
|
|
let result = links.instantiations!.get(id);
|
|
if (!result) {
|
|
const newMapper = createTypeMapper(typeParameters, typeArguments);
|
|
result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((<DeferredTypeReference>type).target, (<DeferredTypeReference>type).node, newMapper) :
|
|
target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(<MappedType>target, newMapper) :
|
|
instantiateAnonymousType(target, newMapper);
|
|
links.instantiations!.set(id, result);
|
|
}
|
|
return result;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function maybeTypeParameterReference(node: Node) {
|
|
return !(node.kind === SyntaxKind.QualifiedName ||
|
|
node.parent.kind === SyntaxKind.TypeReference && (<TypeReferenceNode>node.parent).typeArguments && node === (<TypeReferenceNode>node.parent).typeName ||
|
|
node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier);
|
|
}
|
|
|
|
function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) {
|
|
// If the type parameter doesn't have exactly one declaration, if there are invening statement blocks
|
|
// between the node and the type parameter declaration, if the node contains actual references to the
|
|
// type parameter, or if the node contains type queries, we consider the type parameter possibly referenced.
|
|
if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) {
|
|
const container = tp.symbol.declarations[0].parent;
|
|
for (let n = node; n !== container; n = n.parent) {
|
|
if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((<ConditionalTypeNode>n).extendsType, containsReference)) {
|
|
return true;
|
|
}
|
|
}
|
|
return !!forEachChild(node, containsReference);
|
|
}
|
|
return true;
|
|
function containsReference(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ThisType:
|
|
return !!tp.isThisType;
|
|
case SyntaxKind.Identifier:
|
|
return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) &&
|
|
getTypeFromTypeNode(<TypeNode>node) === tp;
|
|
case SyntaxKind.TypeQuery:
|
|
return true;
|
|
}
|
|
return !!forEachChild(node, containsReference);
|
|
}
|
|
}
|
|
|
|
function getHomomorphicTypeVariable(type: MappedType) {
|
|
const constraintType = getConstraintTypeFromMappedType(type);
|
|
if (constraintType.flags & TypeFlags.Index) {
|
|
const typeVariable = getActualTypeVariable((<IndexType>constraintType).type);
|
|
if (typeVariable.flags & TypeFlags.TypeParameter) {
|
|
return <TypeParameter>typeVariable;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
|
|
// For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping
|
|
// operation depends on T as follows:
|
|
// * If T is a primitive type no mapping is performed and the result is simply T.
|
|
// * If T is a union type we distribute the mapped type over the union.
|
|
// * If T is an array we map to an array where the element type has been transformed.
|
|
// * If T is a tuple we map to a tuple where the element types have been transformed.
|
|
// * Otherwise we map to an object type where the type of each property has been transformed.
|
|
// For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } |
|
|
// { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce
|
|
// { [P in keyof A]: X } | undefined.
|
|
const typeVariable = getHomomorphicTypeVariable(type);
|
|
if (typeVariable) {
|
|
const mappedTypeVariable = instantiateType(typeVariable, mapper);
|
|
if (typeVariable !== mappedTypeVariable) {
|
|
return mapType(getReducedType(mappedTypeVariable), t => {
|
|
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) {
|
|
const replacementMapper = prependTypeMapping(typeVariable, t, mapper);
|
|
return isArrayType(t) ? instantiateMappedArrayType(t, type, replacementMapper) :
|
|
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
|
|
instantiateAnonymousType(type, replacementMapper);
|
|
}
|
|
return t;
|
|
});
|
|
}
|
|
}
|
|
return instantiateAnonymousType(type, mapper);
|
|
}
|
|
|
|
function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) {
|
|
return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state;
|
|
}
|
|
|
|
function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) {
|
|
const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper);
|
|
return elementType === errorType ? errorType :
|
|
createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType)));
|
|
}
|
|
|
|
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
|
|
const minLength = tupleType.target.minLength;
|
|
const elementTypes = map(getTypeArguments(tupleType), (_, i) =>
|
|
instantiateMappedTypeTemplate(mappedType, getLiteralType("" + i), i >= minLength, mapper));
|
|
const modifiers = getMappedTypeModifiers(mappedType);
|
|
const newMinLength = modifiers & MappedTypeModifiers.IncludeOptional ? 0 :
|
|
modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) :
|
|
minLength;
|
|
const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers);
|
|
return contains(elementTypes, errorType) ? errorType :
|
|
createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames);
|
|
}
|
|
|
|
function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) {
|
|
const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key);
|
|
const propType = instantiateType(getTemplateTypeFromMappedType(<MappedType>type.target || type), templateMapper);
|
|
const modifiers = getMappedTypeModifiers(type);
|
|
return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType) :
|
|
strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) :
|
|
propType;
|
|
}
|
|
|
|
function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType {
|
|
const result = <AnonymousType>createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol);
|
|
if (type.objectFlags & ObjectFlags.Mapped) {
|
|
(<MappedType>result).declaration = (<MappedType>type).declaration;
|
|
// C.f. instantiateSignature
|
|
const origTypeParameter = getTypeParameterFromMappedType(<MappedType>type);
|
|
const freshTypeParameter = cloneTypeParameter(origTypeParameter);
|
|
(<MappedType>result).typeParameter = freshTypeParameter;
|
|
mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper);
|
|
freshTypeParameter.mapper = mapper;
|
|
}
|
|
result.target = type;
|
|
result.mapper = mapper;
|
|
result.aliasSymbol = type.aliasSymbol;
|
|
result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper);
|
|
return result;
|
|
}
|
|
|
|
function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper): Type {
|
|
const root = type.root;
|
|
if (root.outerTypeParameters) {
|
|
// We are instantiating a conditional type that has one or more type parameters in scope. Apply the
|
|
// mapper to the type parameters to produce the effective list of type arguments, and compute the
|
|
// instantiation cache key from the type IDs of the type arguments.
|
|
const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper));
|
|
const id = getTypeListId(typeArguments);
|
|
let result = root.instantiations!.get(id);
|
|
if (!result) {
|
|
const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments);
|
|
result = instantiateConditionalType(root, newMapper);
|
|
root.instantiations!.set(id, result);
|
|
}
|
|
return result;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function instantiateConditionalType(root: ConditionalRoot, mapper: TypeMapper): Type {
|
|
// Check if we have a conditional type where the check type is a naked type parameter. If so,
|
|
// the conditional type is distributive over union types and when T is instantiated to a union
|
|
// type A | B, we produce (A extends U ? X : Y) | (B extends U ? X : Y).
|
|
if (root.isDistributive) {
|
|
const checkType = <TypeParameter>root.checkType;
|
|
const instantiatedType = getMappedType(checkType, mapper);
|
|
if (checkType !== instantiatedType && instantiatedType.flags & (TypeFlags.Union | TypeFlags.Never)) {
|
|
return mapType(instantiatedType, t => getConditionalType(root, prependTypeMapping(checkType, t, mapper)));
|
|
}
|
|
}
|
|
return getConditionalType(root, mapper);
|
|
}
|
|
|
|
function instantiateType(type: Type, mapper: TypeMapper | undefined): Type;
|
|
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined;
|
|
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined {
|
|
if (!type || !mapper) {
|
|
return type;
|
|
}
|
|
if (instantiationDepth === 50 || instantiationCount >= 5000000) {
|
|
// We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing
|
|
// with a combination of infinite generic types that perpetually generate new type identities. We stop
|
|
// the recursion here by yielding the error type.
|
|
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
|
|
return errorType;
|
|
}
|
|
totalInstantiationCount++;
|
|
instantiationCount++;
|
|
instantiationDepth++;
|
|
const result = instantiateTypeWorker(type, mapper);
|
|
instantiationDepth--;
|
|
return result;
|
|
}
|
|
|
|
function instantiateTypeWorker(type: Type, mapper: TypeMapper): Type {
|
|
const flags = type.flags;
|
|
if (flags & TypeFlags.TypeParameter) {
|
|
return getMappedType(type, mapper);
|
|
}
|
|
if (flags & TypeFlags.Object) {
|
|
const objectFlags = (<ObjectType>type).objectFlags;
|
|
if (objectFlags & ObjectFlags.Anonymous) {
|
|
// If the anonymous type originates in a declaration of a function, method, class, or
|
|
// interface, in an object type literal, or in an object literal expression, we may need
|
|
// to instantiate the type because it might reference a type parameter.
|
|
return couldContainTypeVariables(type) ?
|
|
getObjectTypeInstantiation(<AnonymousType>type, mapper) : type;
|
|
}
|
|
if (objectFlags & ObjectFlags.Mapped) {
|
|
return getObjectTypeInstantiation(<AnonymousType>type, mapper);
|
|
}
|
|
if (objectFlags & ObjectFlags.Reference) {
|
|
if ((<TypeReference>type).node) {
|
|
return getObjectTypeInstantiation(<TypeReference>type, mapper);
|
|
}
|
|
const resolvedTypeArguments = (<TypeReference>type).resolvedTypeArguments;
|
|
const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper);
|
|
return newTypeArguments !== resolvedTypeArguments ? createTypeReference((<TypeReference>type).target, newTypeArguments) : type;
|
|
}
|
|
return type;
|
|
}
|
|
if ((flags & TypeFlags.Intersection) || (flags & TypeFlags.Union && !(flags & TypeFlags.Primitive))) {
|
|
if (!couldContainTypeVariables(type)) {
|
|
return type;
|
|
}
|
|
const types = (<UnionOrIntersectionType>type).types;
|
|
const newTypes = instantiateTypes(types, mapper);
|
|
return newTypes === types
|
|
? type
|
|
: (flags & TypeFlags.Intersection)
|
|
? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper))
|
|
: getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
|
|
}
|
|
if (flags & TypeFlags.Index) {
|
|
return getIndexType(instantiateType((<IndexType>type).type, mapper));
|
|
}
|
|
if (flags & TypeFlags.IndexedAccess) {
|
|
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
|
|
}
|
|
if (flags & TypeFlags.Conditional) {
|
|
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
|
|
}
|
|
if (flags & TypeFlags.Substitution) {
|
|
const maybeVariable = instantiateType((<SubstitutionType>type).typeVariable, mapper);
|
|
if (maybeVariable.flags & TypeFlags.TypeVariable) {
|
|
return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((<SubstitutionType>type).substitute, mapper));
|
|
}
|
|
else {
|
|
const sub = instantiateType((<SubstitutionType>type).substitute, mapper);
|
|
if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) {
|
|
return maybeVariable;
|
|
}
|
|
return sub;
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getPermissiveInstantiation(type: Type) {
|
|
return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type :
|
|
type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper));
|
|
}
|
|
|
|
function getRestrictiveInstantiation(type: Type) {
|
|
if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) {
|
|
return type;
|
|
}
|
|
if (type.restrictiveInstantiation) {
|
|
return type.restrictiveInstantiation;
|
|
}
|
|
type.restrictiveInstantiation = instantiateType(type, restrictiveMapper);
|
|
// We set the following so we don't attempt to set the restrictive instance of a restrictive instance
|
|
// which is redundant - we'll produce new type identities, but all type params have already been mapped.
|
|
// This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint"
|
|
// assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters
|
|
// are constrained to `unknown` and produce tons of false positives/negatives!
|
|
type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation;
|
|
return type.restrictiveInstantiation;
|
|
}
|
|
|
|
function instantiateIndexInfo(info: IndexInfo | undefined, mapper: TypeMapper): IndexInfo | undefined {
|
|
return info && createIndexInfo(instantiateType(info.type, mapper), info.isReadonly, info.declaration);
|
|
}
|
|
|
|
// Returns true if the given expression contains (at any level of nesting) a function or arrow expression
|
|
// that is subject to contextual typing.
|
|
function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean {
|
|
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
|
|
switch (node.kind) {
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type
|
|
return isContextSensitiveFunctionLikeDeclaration(<FunctionExpression | ArrowFunction | MethodDeclaration>node);
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
return some((<ObjectLiteralExpression>node).properties, isContextSensitive);
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return some((<ArrayLiteralExpression>node).elements, isContextSensitive);
|
|
case SyntaxKind.ConditionalExpression:
|
|
return isContextSensitive((<ConditionalExpression>node).whenTrue) ||
|
|
isContextSensitive((<ConditionalExpression>node).whenFalse);
|
|
case SyntaxKind.BinaryExpression:
|
|
return ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken || (<BinaryExpression>node).operatorToken.kind === SyntaxKind.QuestionQuestionToken) &&
|
|
(isContextSensitive((<BinaryExpression>node).left) || isContextSensitive((<BinaryExpression>node).right));
|
|
case SyntaxKind.PropertyAssignment:
|
|
return isContextSensitive((<PropertyAssignment>node).initializer);
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return isContextSensitive((<ParenthesizedExpression>node).expression);
|
|
case SyntaxKind.JsxAttributes:
|
|
return some((<JsxAttributes>node).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive);
|
|
case SyntaxKind.JsxAttribute: {
|
|
// If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive.
|
|
const { initializer } = node as JsxAttribute;
|
|
return !!initializer && isContextSensitive(initializer);
|
|
}
|
|
case SyntaxKind.JsxExpression: {
|
|
// It is possible to that node.expression is undefined (e.g <div x={} />)
|
|
const { expression } = node as JsxExpression;
|
|
return !!expression && isContextSensitive(expression);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
|
|
return (!isFunctionDeclaration(node) || isInJSFile(node) && !!getTypeForDeclarationFromJSDocComment(node)) &&
|
|
(hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node));
|
|
}
|
|
|
|
function hasContextSensitiveParameters(node: FunctionLikeDeclaration) {
|
|
// Functions with type parameters are not context sensitive.
|
|
if (!node.typeParameters) {
|
|
// Functions with any parameters that lack type annotations are context sensitive.
|
|
if (some(node.parameters, p => !getEffectiveTypeAnnotationNode(p))) {
|
|
return true;
|
|
}
|
|
if (node.kind !== SyntaxKind.ArrowFunction) {
|
|
// If the first parameter is not an explicit 'this' parameter, then the function has
|
|
// an implicit 'this' parameter which is subject to contextual typing.
|
|
const parameter = firstOrUndefined(node.parameters);
|
|
if (!(parameter && parameterIsThisKeyword(parameter))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) {
|
|
// TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value.
|
|
return !getEffectiveReturnTypeNode(node) && !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body);
|
|
}
|
|
|
|
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
|
|
return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
|
|
isContextSensitiveFunctionLikeDeclaration(func);
|
|
}
|
|
|
|
function getTypeWithoutSignatures(type: Type): Type {
|
|
if (type.flags & TypeFlags.Object) {
|
|
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
|
|
if (resolved.constructSignatures.length || resolved.callSignatures.length) {
|
|
const result = createObjectType(ObjectFlags.Anonymous, type.symbol);
|
|
result.members = resolved.members;
|
|
result.properties = resolved.properties;
|
|
result.callSignatures = emptyArray;
|
|
result.constructSignatures = emptyArray;
|
|
return result;
|
|
}
|
|
}
|
|
else if (type.flags & TypeFlags.Intersection) {
|
|
return getIntersectionType(map((<IntersectionType>type).types, getTypeWithoutSignatures));
|
|
}
|
|
return type;
|
|
}
|
|
|
|
// TYPE CHECKING
|
|
|
|
function isTypeIdenticalTo(source: Type, target: Type): boolean {
|
|
return isTypeRelatedTo(source, target, identityRelation);
|
|
}
|
|
|
|
function compareTypesIdentical(source: Type, target: Type): Ternary {
|
|
return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False;
|
|
}
|
|
|
|
function compareTypesAssignable(source: Type, target: Type): Ternary {
|
|
return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False;
|
|
}
|
|
|
|
function compareTypesSubtypeOf(source: Type, target: Type): Ternary {
|
|
return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False;
|
|
}
|
|
|
|
function isTypeSubtypeOf(source: Type, target: Type): boolean {
|
|
return isTypeRelatedTo(source, target, subtypeRelation);
|
|
}
|
|
|
|
function isTypeAssignableTo(source: Type, target: Type): boolean {
|
|
return isTypeRelatedTo(source, target, assignableRelation);
|
|
}
|
|
|
|
// An object type S is considered to be derived from an object type T if
|
|
// S is a union type and every constituent of S is derived from T,
|
|
// T is a union type and S is derived from at least one constituent of T, or
|
|
// S is a type variable with a base constraint that is derived from T,
|
|
// T is one of the global types Object and Function and S is a subtype of T, or
|
|
// T occurs directly or indirectly in an 'extends' clause of S.
|
|
// Note that this check ignores type parameters and only considers the
|
|
// inheritance hierarchy.
|
|
function isTypeDerivedFrom(source: Type, target: Type): boolean {
|
|
return source.flags & TypeFlags.Union ? every((<UnionType>source).types, t => isTypeDerivedFrom(t, target)) :
|
|
target.flags & TypeFlags.Union ? some((<UnionType>target).types, t => isTypeDerivedFrom(source, t)) :
|
|
source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) :
|
|
target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) :
|
|
target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) :
|
|
hasBaseType(source, getTargetType(target));
|
|
}
|
|
|
|
/**
|
|
* This is *not* a bi-directional relationship.
|
|
* If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'.
|
|
*
|
|
* A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T.
|
|
* It is used to check following cases:
|
|
* - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`).
|
|
* - the types of `case` clause expressions and their respective `switch` expressions.
|
|
* - the type of an expression in a type assertion with the type being asserted.
|
|
*/
|
|
function isTypeComparableTo(source: Type, target: Type): boolean {
|
|
return isTypeRelatedTo(source, target, comparableRelation);
|
|
}
|
|
|
|
function areTypesComparable(type1: Type, type2: Type): boolean {
|
|
return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1);
|
|
}
|
|
|
|
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[] }): boolean {
|
|
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject);
|
|
}
|
|
|
|
/**
|
|
* Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to
|
|
* attempt to issue more specific errors on, for example, specific object literal properties or tuple members.
|
|
*/
|
|
function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
|
|
return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined);
|
|
}
|
|
|
|
function checkTypeRelatedToAndOptionallyElaborate(
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
errorNode: Node | undefined,
|
|
expr: Expression | undefined,
|
|
headMessage: DiagnosticMessage | undefined,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
|
|
): boolean {
|
|
if (isTypeRelatedTo(source, target, relation)) return true;
|
|
if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) {
|
|
return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isOrHasGenericConditional(type: Type): boolean {
|
|
return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional)));
|
|
}
|
|
|
|
function elaborateError(
|
|
node: Expression | undefined,
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
headMessage: DiagnosticMessage | undefined,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
|
|
): boolean {
|
|
if (!node || isOrHasGenericConditional(target)) return false;
|
|
if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined)
|
|
&& elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) {
|
|
return true;
|
|
}
|
|
switch (node.kind) {
|
|
case SyntaxKind.JsxExpression:
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer);
|
|
case SyntaxKind.BinaryExpression:
|
|
switch ((node as BinaryExpression).operatorToken.kind) {
|
|
case SyntaxKind.EqualsToken:
|
|
case SyntaxKind.CommaToken:
|
|
return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer);
|
|
}
|
|
break;
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer);
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer);
|
|
case SyntaxKind.JsxAttributes:
|
|
return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer);
|
|
case SyntaxKind.ArrowFunction:
|
|
return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function elaborateDidYouMeanToCallOrConstruct(
|
|
node: Expression,
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
headMessage: DiagnosticMessage | undefined,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
|
|
): boolean {
|
|
const callSignatures = getSignaturesOfType(source, SignatureKind.Call);
|
|
const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct);
|
|
for (const signatures of [constructSignatures, callSignatures]) {
|
|
if (some(signatures, s => {
|
|
const returnType = getReturnTypeOfSignature(s);
|
|
return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined);
|
|
})) {
|
|
const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {};
|
|
checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj);
|
|
const diagnostic = resultObj.errors![resultObj.errors!.length - 1];
|
|
addRelatedInfo(diagnostic, createDiagnosticForNode(
|
|
node,
|
|
signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression
|
|
));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function elaborateArrowFunction(
|
|
node: ArrowFunction,
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
|
|
): boolean {
|
|
// Don't elaborate blocks
|
|
if (isBlock(node.body)) {
|
|
return false;
|
|
}
|
|
// Or functions with annotated parameter types
|
|
if (some(node.parameters, ts.hasType)) {
|
|
return false;
|
|
}
|
|
const sourceSig = getSingleCallSignature(source);
|
|
if (!sourceSig) {
|
|
return false;
|
|
}
|
|
const targetSignatures = getSignaturesOfType(target, SignatureKind.Call);
|
|
if (!length(targetSignatures)) {
|
|
return false;
|
|
}
|
|
const returnExpression = node.body;
|
|
const sourceReturn = getReturnTypeOfSignature(sourceSig);
|
|
const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature));
|
|
if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) {
|
|
const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer);
|
|
if (elaborated) {
|
|
return elaborated;
|
|
}
|
|
const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {};
|
|
checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*message*/ undefined, containingMessageChain, resultObj);
|
|
if (resultObj.errors) {
|
|
if (target.symbol && length(target.symbol.declarations)) {
|
|
addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode(
|
|
target.symbol.declarations[0],
|
|
Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature,
|
|
));
|
|
}
|
|
if ((getFunctionFlags(node) & FunctionFlags.Async) === 0
|
|
// exclude cases where source itself is promisy - this way we don't make a suggestion when relating
|
|
// an IPromise and a Promise that are slightly different
|
|
&& !getTypeOfPropertyOfType(sourceReturn, "then" as __String)
|
|
&& checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined)
|
|
) {
|
|
addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode(
|
|
node,
|
|
Diagnostics.Did_you_mean_to_mark_this_function_as_async
|
|
));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) {
|
|
const idx = getIndexedAccessTypeOrUndefined(target, nameType);
|
|
if (idx) {
|
|
return idx;
|
|
}
|
|
if (target.flags & TypeFlags.Union) {
|
|
const best = getBestMatchingType(source, target as UnionType);
|
|
if (best) {
|
|
return getIndexedAccessTypeOrUndefined(best, nameType);
|
|
}
|
|
}
|
|
}
|
|
|
|
type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>;
|
|
/**
|
|
* For every element returned from the iterator, checks that element to issue an error on a property of that element's type
|
|
* If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError`
|
|
* Otherwise, we issue an error on _every_ element which fail the assignability check
|
|
*/
|
|
function elaborateElementwise(
|
|
iterator: ElaborationIterator,
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
|
|
) {
|
|
// Assignability failure - check each prop individually, and if that fails, fall back on the bad error span
|
|
let reportedError = false;
|
|
for (let status = iterator.next(); !status.done; status = iterator.next()) {
|
|
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value;
|
|
const targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType);
|
|
if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables
|
|
const sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType);
|
|
if (sourcePropType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) {
|
|
const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer);
|
|
if (elaborated) {
|
|
reportedError = true;
|
|
}
|
|
else {
|
|
// Issue error on the prop itself, since the prop couldn't elaborate the error
|
|
const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {};
|
|
// Use the expression type, if available
|
|
const specificSource = next ? checkExpressionForMutableLocation(next, CheckMode.Normal, sourcePropType) : sourcePropType;
|
|
const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
|
|
if (result && specificSource !== sourcePropType) {
|
|
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
|
|
checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
|
|
}
|
|
if (resultObj.errors) {
|
|
const reportedDiag = resultObj.errors[resultObj.errors.length - 1];
|
|
const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
|
|
const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined;
|
|
|
|
let issuedElaboration = false;
|
|
if (!targetProp) {
|
|
const indexInfo = isTypeAssignableToKind(nameType, TypeFlags.NumberLike) && getIndexInfoOfType(target, IndexKind.Number) ||
|
|
getIndexInfoOfType(target, IndexKind.String) ||
|
|
undefined;
|
|
if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) {
|
|
issuedElaboration = true;
|
|
addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature));
|
|
}
|
|
}
|
|
|
|
if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) {
|
|
const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations[0] : target.symbol.declarations[0];
|
|
if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) {
|
|
addRelatedInfo(reportedDiag, createDiagnosticForNode(
|
|
targetNode,
|
|
Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1,
|
|
propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType),
|
|
typeToString(target)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
reportedError = true;
|
|
}
|
|
}
|
|
}
|
|
return reportedError;
|
|
}
|
|
|
|
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
|
|
if (!length(node.properties)) return;
|
|
for (const prop of node.properties) {
|
|
if (isJsxSpreadAttribute(prop)) continue;
|
|
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getLiteralType(idText(prop.name)) };
|
|
}
|
|
}
|
|
|
|
function *generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator {
|
|
if (!length(node.children)) return;
|
|
let memberOffset = 0;
|
|
for (let i = 0; i < node.children.length; i++) {
|
|
const child = node.children[i];
|
|
const nameType = getLiteralType(i - memberOffset);
|
|
const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic);
|
|
if (elem) {
|
|
yield elem;
|
|
}
|
|
else {
|
|
memberOffset++;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) {
|
|
switch (child.kind) {
|
|
case SyntaxKind.JsxExpression:
|
|
// child is of the type of the expression
|
|
return { errorNode: child, innerExpression: child.expression, nameType };
|
|
case SyntaxKind.JsxText:
|
|
if (child.containsOnlyTriviaWhiteSpaces) {
|
|
break; // Whitespace only jsx text isn't real jsx text
|
|
}
|
|
// child is a string
|
|
return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() };
|
|
case SyntaxKind.JsxElement:
|
|
case SyntaxKind.JsxSelfClosingElement:
|
|
case SyntaxKind.JsxFragment:
|
|
// child is of type JSX.Element
|
|
return { errorNode: child, innerExpression: child, nameType };
|
|
default:
|
|
return Debug.assertNever(child, "Found invalid jsx child");
|
|
}
|
|
}
|
|
|
|
function getSemanticJsxChildren(children: NodeArray<JsxChild>) {
|
|
return filter(children, i => !isJsxText(i) || !i.containsOnlyTriviaWhiteSpaces);
|
|
}
|
|
|
|
function elaborateJsxComponents(
|
|
node: JsxAttributes,
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
|
|
) {
|
|
let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer);
|
|
let invalidTextDiagnostic: DiagnosticMessage | undefined;
|
|
if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) {
|
|
const containingElement = node.parent.parent;
|
|
const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
|
|
const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName);
|
|
const childrenNameType = getLiteralType(childrenPropName);
|
|
const childrenTargetType = getIndexedAccessType(target, childrenNameType);
|
|
const validChildren = getSemanticJsxChildren(containingElement.children);
|
|
if (!length(validChildren)) {
|
|
return result;
|
|
}
|
|
const moreThanOneRealChildren = length(validChildren) > 1;
|
|
const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType);
|
|
const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t));
|
|
if (moreThanOneRealChildren) {
|
|
if (arrayLikeTargetParts !== neverType) {
|
|
const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal));
|
|
const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic);
|
|
result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result;
|
|
}
|
|
else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) {
|
|
// arity mismatch
|
|
result = true;
|
|
const diag = error(
|
|
containingElement.openingElement.tagName,
|
|
Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided,
|
|
childrenPropName,
|
|
typeToString(childrenTargetType)
|
|
);
|
|
if (errorOutputContainer && errorOutputContainer.skipLogging) {
|
|
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (nonArrayLikeTargetParts !== neverType) {
|
|
const child = validChildren[0];
|
|
const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic);
|
|
if (elem) {
|
|
result = elaborateElementwise(
|
|
(function*() { yield elem; })(),
|
|
source,
|
|
target,
|
|
relation,
|
|
containingMessageChain,
|
|
errorOutputContainer
|
|
) || result;
|
|
}
|
|
}
|
|
else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) {
|
|
// arity mismatch
|
|
result = true;
|
|
const diag = error(
|
|
containingElement.openingElement.tagName,
|
|
Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided,
|
|
childrenPropName,
|
|
typeToString(childrenTargetType)
|
|
);
|
|
if (errorOutputContainer && errorOutputContainer.skipLogging) {
|
|
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
|
|
function getInvalidTextualChildDiagnostic() {
|
|
if (!invalidTextDiagnostic) {
|
|
const tagNameText = getTextOfNode(node.parent.tagName);
|
|
const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
|
|
const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName);
|
|
const childrenTargetType = getIndexedAccessType(target, getLiteralType(childrenPropName));
|
|
const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2;
|
|
invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(/*_dummy*/ undefined, diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) };
|
|
}
|
|
return invalidTextDiagnostic;
|
|
}
|
|
}
|
|
|
|
function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator {
|
|
const len = length(node.elements);
|
|
if (!len) return;
|
|
for (let i = 0; i < len; i++) {
|
|
// Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature
|
|
if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue;
|
|
const elem = node.elements[i];
|
|
if (isOmittedExpression(elem)) continue;
|
|
const nameType = getLiteralType(i);
|
|
yield { errorNode: elem, innerExpression: elem, nameType };
|
|
}
|
|
}
|
|
|
|
function elaborateArrayLiteral(
|
|
node: ArrayLiteralExpression,
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
|
|
) {
|
|
if (target.flags & TypeFlags.Primitive) return false;
|
|
if (isTupleLikeType(source)) {
|
|
return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer);
|
|
}
|
|
// recreate a tuple from the elements, if possible
|
|
// Since we're re-doing the expression type, we need to reapply the contextual type
|
|
const oldContext = node.contextualType;
|
|
node.contextualType = target;
|
|
try {
|
|
const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true);
|
|
node.contextualType = oldContext;
|
|
if (isTupleLikeType(tupleizedType)) {
|
|
return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer);
|
|
}
|
|
return false;
|
|
}
|
|
finally {
|
|
node.contextualType = oldContext;
|
|
}
|
|
}
|
|
|
|
function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator {
|
|
if (!length(node.properties)) return;
|
|
for (const prop of node.properties) {
|
|
if (isSpreadAssignment(prop)) continue;
|
|
const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique);
|
|
if (!type || (type.flags & TypeFlags.Never)) {
|
|
continue;
|
|
}
|
|
switch (prop.kind) {
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
yield { errorNode: prop.name, innerExpression: undefined, nameType: type };
|
|
break;
|
|
case SyntaxKind.PropertyAssignment:
|
|
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined };
|
|
break;
|
|
default:
|
|
Debug.assertNever(prop);
|
|
}
|
|
}
|
|
}
|
|
|
|
function elaborateObjectLiteral(
|
|
node: ObjectLiteralExpression,
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
|
|
) {
|
|
if (target.flags & TypeFlags.Primitive) return false;
|
|
return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer);
|
|
}
|
|
|
|
/**
|
|
* This is *not* a bi-directional relationship.
|
|
* If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'.
|
|
*/
|
|
function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
|
|
return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain);
|
|
}
|
|
|
|
function isSignatureAssignableTo(source: Signature,
|
|
target: Signature,
|
|
ignoreReturnTypes: boolean): boolean {
|
|
return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false,
|
|
/*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False;
|
|
}
|
|
|
|
type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void;
|
|
|
|
/**
|
|
* Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any`
|
|
*/
|
|
function isAnySignature(s: Signature) {
|
|
return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 &&
|
|
signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) &&
|
|
isTypeAny(getReturnTypeOfSignature(s));
|
|
}
|
|
|
|
/**
|
|
* See signatureRelatedTo, compareSignaturesIdentical
|
|
*/
|
|
function compareSignaturesRelated(source: Signature,
|
|
target: Signature,
|
|
checkMode: SignatureCheckMode,
|
|
reportErrors: boolean,
|
|
errorReporter: ErrorReporter | undefined,
|
|
incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined,
|
|
compareTypes: TypeComparer,
|
|
reportUnreliableMarkers: TypeMapper | undefined): Ternary {
|
|
// TODO (drosen): De-duplicate code between related functions.
|
|
if (source === target) {
|
|
return Ternary.True;
|
|
}
|
|
|
|
if (isAnySignature(target)) {
|
|
return Ternary.True;
|
|
}
|
|
|
|
const targetCount = getParameterCount(target);
|
|
const sourceHasMoreParameters = !hasEffectiveRestParameter(target) &&
|
|
(checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount);
|
|
if (sourceHasMoreParameters) {
|
|
return Ternary.False;
|
|
}
|
|
|
|
if (source.typeParameters && source.typeParameters !== target.typeParameters) {
|
|
target = getCanonicalSignature(target);
|
|
source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes);
|
|
}
|
|
|
|
const sourceCount = getParameterCount(source);
|
|
const sourceRestType = getNonArrayRestType(source);
|
|
const targetRestType = getNonArrayRestType(target);
|
|
if (sourceRestType || targetRestType) {
|
|
void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers);
|
|
}
|
|
if (sourceRestType && targetRestType && sourceCount !== targetCount) {
|
|
// We're not able to relate misaligned complex rest parameters
|
|
return Ternary.False;
|
|
}
|
|
|
|
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
|
|
const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
|
|
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor;
|
|
let result = Ternary.True;
|
|
|
|
const sourceThisType = getThisTypeOfSignature(source);
|
|
if (sourceThisType && sourceThisType !== voidType) {
|
|
const targetThisType = getThisTypeOfSignature(target);
|
|
if (targetThisType) {
|
|
// void sources are assignable to anything.
|
|
const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false)
|
|
|| compareTypes(targetThisType, sourceThisType, reportErrors);
|
|
if (!related) {
|
|
if (reportErrors) {
|
|
errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
}
|
|
|
|
const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount);
|
|
const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1;
|
|
|
|
for (let i = 0; i < paramCount; i++) {
|
|
const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : getTypeAtPosition(source, i);
|
|
const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : getTypeAtPosition(target, i);
|
|
// In order to ensure that any generic type Foo<T> is at least co-variant with respect to T no matter
|
|
// how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions,
|
|
// they naturally relate only contra-variantly). However, if the source and target parameters both have
|
|
// function types with a single call signature, we know we are relating two callback parameters. In
|
|
// that case it is sufficient to only relate the parameters of the signatures co-variantly because,
|
|
// similar to return values, callback parameters are output positions. This means that a Promise<T>,
|
|
// where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant)
|
|
// with respect to T.
|
|
const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
|
|
const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType));
|
|
const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) &&
|
|
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
|
|
let related = callbacks ?
|
|
compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
|
|
!(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
|
|
// With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void
|
|
if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) {
|
|
related = Ternary.False;
|
|
}
|
|
if (!related) {
|
|
if (reportErrors) {
|
|
errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
|
|
unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)),
|
|
unescapeLeadingUnderscores(getParameterNameAtPosition(target, i)));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
|
|
if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) {
|
|
// If a signature resolution is already in-flight, skip issuing a circularity error
|
|
// here and just use the `any` type directly
|
|
const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType
|
|
: target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol))
|
|
: getReturnTypeOfSignature(target);
|
|
if (targetReturnType === voidType) {
|
|
return result;
|
|
}
|
|
const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType
|
|
: source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol))
|
|
: getReturnTypeOfSignature(source);
|
|
|
|
// The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions
|
|
const targetTypePredicate = getTypePredicateOfSignature(target);
|
|
if (targetTypePredicate) {
|
|
const sourceTypePredicate = getTypePredicateOfSignature(source);
|
|
if (sourceTypePredicate) {
|
|
result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes);
|
|
}
|
|
else if (isIdentifierTypePredicate(targetTypePredicate)) {
|
|
if (reportErrors) {
|
|
errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
else {
|
|
// When relating callback signatures, we still need to relate return types bi-variantly as otherwise
|
|
// the containing type wouldn't be co-variant. For example, interface Foo<T> { add(cb: () => T): void }
|
|
// wouldn't be co-variant for T without this rule.
|
|
result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
|
|
compareTypes(sourceReturnType, targetReturnType, reportErrors);
|
|
if (!result && reportErrors && incompatibleErrorReporter) {
|
|
incompatibleErrorReporter(sourceReturnType, targetReturnType);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function compareTypePredicateRelatedTo(
|
|
source: TypePredicate,
|
|
target: TypePredicate,
|
|
reportErrors: boolean,
|
|
errorReporter: ErrorReporter | undefined,
|
|
compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary {
|
|
if (source.kind !== target.kind) {
|
|
if (reportErrors) {
|
|
errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard);
|
|
errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
|
|
if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) {
|
|
if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) {
|
|
if (reportErrors) {
|
|
errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName);
|
|
errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
|
|
const related = source.type === target.type ? Ternary.True :
|
|
source.type && target.type ? compareTypes(source.type, target.type, reportErrors) :
|
|
Ternary.False;
|
|
if (related === Ternary.False && reportErrors) {
|
|
errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target));
|
|
}
|
|
return related;
|
|
}
|
|
|
|
function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean {
|
|
const erasedSource = getErasedSignature(implementation);
|
|
const erasedTarget = getErasedSignature(overload);
|
|
|
|
// First see if the return types are compatible in either direction.
|
|
const sourceReturnType = getReturnTypeOfSignature(erasedSource);
|
|
const targetReturnType = getReturnTypeOfSignature(erasedTarget);
|
|
if (targetReturnType === voidType
|
|
|| isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation)
|
|
|| isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) {
|
|
|
|
return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isEmptyResolvedType(t: ResolvedType) {
|
|
return t !== anyFunctionType &&
|
|
t.properties.length === 0 &&
|
|
t.callSignatures.length === 0 &&
|
|
t.constructSignatures.length === 0 &&
|
|
!t.stringIndexInfo &&
|
|
!t.numberIndexInfo;
|
|
}
|
|
|
|
function isEmptyObjectType(type: Type): boolean {
|
|
return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(<ObjectType>type)) :
|
|
type.flags & TypeFlags.NonPrimitive ? true :
|
|
type.flags & TypeFlags.Union ? some((<UnionType>type).types, isEmptyObjectType) :
|
|
type.flags & TypeFlags.Intersection ? every((<UnionType>type).types, isEmptyObjectType) :
|
|
false;
|
|
}
|
|
|
|
function isEmptyAnonymousObjectType(type: Type) {
|
|
return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && isEmptyObjectType(type);
|
|
}
|
|
|
|
function isStringIndexSignatureOnlyType(type: Type): boolean {
|
|
return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfoOfType(type, IndexKind.String) && !getIndexInfoOfType(type, IndexKind.Number) ||
|
|
type.flags & TypeFlags.UnionOrIntersection && every((<UnionOrIntersectionType>type).types, isStringIndexSignatureOnlyType) ||
|
|
false;
|
|
}
|
|
|
|
function isEnumTypeRelatedTo(sourceSymbol: Symbol, targetSymbol: Symbol, errorReporter?: ErrorReporter) {
|
|
if (sourceSymbol === targetSymbol) {
|
|
return true;
|
|
}
|
|
const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol);
|
|
const entry = enumRelation.get(id);
|
|
if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) {
|
|
return !!(entry & RelationComparisonResult.Succeeded);
|
|
}
|
|
if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) {
|
|
enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
|
|
return false;
|
|
}
|
|
const targetEnumType = getTypeOfSymbol(targetSymbol);
|
|
for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) {
|
|
if (property.flags & SymbolFlags.EnumMember) {
|
|
const targetProperty = getPropertyOfType(targetEnumType, property.escapedName);
|
|
if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) {
|
|
if (errorReporter) {
|
|
errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property),
|
|
typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
|
|
enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
|
|
}
|
|
else {
|
|
enumRelation.set(id, RelationComparisonResult.Failed);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
enumRelation.set(id, RelationComparisonResult.Succeeded);
|
|
return true;
|
|
}
|
|
|
|
function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map<RelationComparisonResult>, errorReporter?: ErrorReporter) {
|
|
const s = source.flags;
|
|
const t = target.flags;
|
|
if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true;
|
|
if (t & TypeFlags.Never) return false;
|
|
if (s & TypeFlags.StringLike && t & TypeFlags.String) return true;
|
|
if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral &&
|
|
t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) &&
|
|
(<StringLiteralType>source).value === (<StringLiteralType>target).value) return true;
|
|
if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true;
|
|
if (s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral &&
|
|
t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) &&
|
|
(<NumberLiteralType>source).value === (<NumberLiteralType>target).value) return true;
|
|
if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true;
|
|
if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true;
|
|
if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true;
|
|
if (s & TypeFlags.Enum && t & TypeFlags.Enum && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true;
|
|
if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) {
|
|
if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true;
|
|
if (s & TypeFlags.Literal && t & TypeFlags.Literal &&
|
|
(<LiteralType>source).value === (<LiteralType>target).value &&
|
|
isEnumTypeRelatedTo(getParentOfSymbol(source.symbol)!, getParentOfSymbol(target.symbol)!, errorReporter)) return true;
|
|
}
|
|
if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) return true;
|
|
if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true;
|
|
if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true;
|
|
if (relation === assignableRelation || relation === comparableRelation) {
|
|
if (s & TypeFlags.Any) return true;
|
|
// Type number or any numeric literal type is assignable to any numeric enum type or any
|
|
// numeric enum literal type. This rule exists for backwards compatibility reasons because
|
|
// bit-flag enum types sometimes look like literal enum types with numeric literal values.
|
|
if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && (
|
|
t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isTypeRelatedTo(source: Type, target: Type, relation: Map<RelationComparisonResult>) {
|
|
if (isFreshLiteralType(source)) {
|
|
source = (<FreshableType>source).regularType;
|
|
}
|
|
if (isFreshLiteralType(target)) {
|
|
target = (<FreshableType>target).regularType;
|
|
}
|
|
if (source === target) {
|
|
return true;
|
|
}
|
|
if (relation !== identityRelation) {
|
|
if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) {
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
if (!(source.flags & TypeFlags.UnionOrIntersection) && !(target.flags & TypeFlags.UnionOrIntersection) &&
|
|
source.flags !== target.flags && !(source.flags & TypeFlags.Substructure)) return false;
|
|
}
|
|
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
|
|
const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation));
|
|
if (related !== undefined) {
|
|
return !!(related & RelationComparisonResult.Succeeded);
|
|
}
|
|
}
|
|
if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
|
|
return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) {
|
|
return getObjectFlags(source) & ObjectFlags.JsxAttributes && !isUnhyphenatedJsxName(sourceProp.escapedName);
|
|
}
|
|
|
|
function getNormalizedType(type: Type, writing: boolean): Type {
|
|
while (true) {
|
|
const t = isFreshLiteralType(type) ? (<FreshableType>type).regularType :
|
|
getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).node ? createTypeReference((<TypeReference>type).target, getTypeArguments(<TypeReference>type)) :
|
|
type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) :
|
|
type.flags & TypeFlags.Substitution ? writing ? (<SubstitutionType>type).typeVariable : (<SubstitutionType>type).substitute :
|
|
type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) :
|
|
type;
|
|
if (t === type) break;
|
|
type = t;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Checks if 'source' is related to 'target' (e.g.: is a assignable to).
|
|
* @param source The left-hand-side of the relation.
|
|
* @param target The right-hand-side of the relation.
|
|
* @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'.
|
|
* Used as both to determine which checks are performed and as a cache of previously computed results.
|
|
* @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used.
|
|
* @param headMessage If the error chain should be prepended by a head message, then headMessage will be used.
|
|
* @param containingMessageChain A chain of errors to prepend any new errors found.
|
|
* @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy.
|
|
*/
|
|
function checkTypeRelatedTo(
|
|
source: Type,
|
|
target: Type,
|
|
relation: Map<RelationComparisonResult>,
|
|
errorNode: Node | undefined,
|
|
headMessage?: DiagnosticMessage,
|
|
containingMessageChain?: () => DiagnosticMessageChain | undefined,
|
|
errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean },
|
|
): boolean {
|
|
let errorInfo: DiagnosticMessageChain | undefined;
|
|
let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined;
|
|
let maybeKeys: string[];
|
|
let sourceStack: Type[];
|
|
let targetStack: Type[];
|
|
let maybeCount = 0;
|
|
let depth = 0;
|
|
let expandingFlags = ExpandingFlags.None;
|
|
let overflow = false;
|
|
let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid
|
|
let lastSkippedInfo: [Type, Type] | undefined;
|
|
let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = [];
|
|
|
|
Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking");
|
|
|
|
const result = isRelatedTo(source, target, /*reportErrors*/ !!errorNode, headMessage);
|
|
if (incompatibleStack.length) {
|
|
reportIncompatibleStack();
|
|
}
|
|
if (overflow) {
|
|
const diag = error(errorNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target));
|
|
if (errorOutputContainer) {
|
|
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
|
|
}
|
|
}
|
|
else if (errorInfo) {
|
|
if (containingMessageChain) {
|
|
const chain = containingMessageChain();
|
|
if (chain) {
|
|
concatenateDiagnosticMessageChains(chain, errorInfo);
|
|
errorInfo = chain;
|
|
}
|
|
}
|
|
|
|
let relatedInformation: DiagnosticRelatedInformation[] | undefined;
|
|
// Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement
|
|
if (headMessage && errorNode && !result && source.symbol) {
|
|
const links = getSymbolLinks(source.symbol);
|
|
if (links.originatingImport && !isImportCall(links.originatingImport)) {
|
|
const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined);
|
|
if (helpfulRetry) {
|
|
// Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import
|
|
const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead);
|
|
relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it
|
|
}
|
|
}
|
|
}
|
|
const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation);
|
|
if (relatedInfo) {
|
|
addRelatedInfo(diag, ...relatedInfo);
|
|
}
|
|
if (errorOutputContainer) {
|
|
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
|
|
}
|
|
if (!errorOutputContainer || !errorOutputContainer.skipLogging) {
|
|
diagnostics.add(diag);
|
|
}
|
|
}
|
|
if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) {
|
|
Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error.");
|
|
}
|
|
return result !== Ternary.False;
|
|
|
|
function resetErrorInfo(saved: ReturnType<typeof captureErrorCalculationState>) {
|
|
errorInfo = saved.errorInfo;
|
|
lastSkippedInfo = saved.lastSkippedInfo;
|
|
incompatibleStack = saved.incompatibleStack;
|
|
overrideNextErrorInfo = saved.overrideNextErrorInfo;
|
|
relatedInfo = saved.relatedInfo;
|
|
}
|
|
|
|
function captureErrorCalculationState() {
|
|
return {
|
|
errorInfo,
|
|
lastSkippedInfo,
|
|
incompatibleStack: incompatibleStack.slice(),
|
|
overrideNextErrorInfo,
|
|
relatedInfo: !relatedInfo ? undefined : relatedInfo.slice() as ([DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined)
|
|
};
|
|
}
|
|
|
|
function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) {
|
|
overrideNextErrorInfo++; // Suppress the next relation error
|
|
lastSkippedInfo = undefined; // Reset skipped info cache
|
|
incompatibleStack.push([message, arg0, arg1, arg2, arg3]);
|
|
}
|
|
|
|
function reportIncompatibleStack() {
|
|
const stack = incompatibleStack;
|
|
incompatibleStack = [];
|
|
const info = lastSkippedInfo;
|
|
lastSkippedInfo = undefined;
|
|
if (stack.length === 1) {
|
|
reportError(...stack[0]);
|
|
if (info) {
|
|
// Actually do the last relation error
|
|
reportRelationError(/*headMessage*/ undefined, ...info);
|
|
}
|
|
return;
|
|
}
|
|
// The first error will be the innermost, while the last will be the outermost - so by popping off the end,
|
|
// we can build from left to right
|
|
let path = "";
|
|
const secondaryRootErrors: typeof incompatibleStack = [];
|
|
while (stack.length) {
|
|
const [msg, ...args] = stack.pop()!;
|
|
switch (msg.code) {
|
|
case Diagnostics.Types_of_property_0_are_incompatible.code: {
|
|
// Parenthesize a `new` if there is one
|
|
if (path.indexOf("new ") === 0) {
|
|
path = `(${path})`;
|
|
}
|
|
const str = "" + args[0];
|
|
// If leading, just print back the arg (irrespective of if it's a valid identifier)
|
|
if (path.length === 0) {
|
|
path = `${str}`;
|
|
}
|
|
// Otherwise write a dotted name if possible
|
|
else if (isIdentifierText(str, compilerOptions.target)) {
|
|
path = `${path}.${str}`;
|
|
}
|
|
// Failing that, check if the name is already a computed name
|
|
else if (str[0] === "[" && str[str.length - 1] === "]") {
|
|
path = `${path}${str}`;
|
|
}
|
|
// And finally write out a computed name as a last resort
|
|
else {
|
|
path = `${path}[${str}]`;
|
|
}
|
|
break;
|
|
}
|
|
case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code:
|
|
case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code:
|
|
case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code:
|
|
case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: {
|
|
if (path.length === 0) {
|
|
// Don't flatten signature compatability errors at the start of a chain - instead prefer
|
|
// to unify (the with no arguments bit is excessive for printback) and print them back
|
|
let mappedMsg = msg;
|
|
if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) {
|
|
mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible;
|
|
}
|
|
else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) {
|
|
mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible;
|
|
}
|
|
secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]);
|
|
}
|
|
else {
|
|
const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code ||
|
|
msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code)
|
|
? "new "
|
|
: "";
|
|
const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code ||
|
|
msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code)
|
|
? ""
|
|
: "...";
|
|
path = `${prefix}${path}(${params})`;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return Debug.fail(`Unhandled Diagnostic: ${msg.code}`);
|
|
}
|
|
}
|
|
if (path) {
|
|
reportError(path[path.length - 1] === ")"
|
|
? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types
|
|
: Diagnostics.The_types_of_0_are_incompatible_between_these_types,
|
|
path
|
|
);
|
|
}
|
|
else {
|
|
// Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry
|
|
secondaryRootErrors.shift();
|
|
}
|
|
for (const [msg, ...args] of secondaryRootErrors) {
|
|
const originalValue = msg.elidedInCompatabilityPyramid;
|
|
msg.elidedInCompatabilityPyramid = false; // Teporarily override elision to ensure error is reported
|
|
reportError(msg, ...args);
|
|
msg.elidedInCompatabilityPyramid = originalValue;
|
|
}
|
|
if (info) {
|
|
// Actually do the last relation error
|
|
reportRelationError(/*headMessage*/ undefined, ...info);
|
|
}
|
|
}
|
|
|
|
function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void {
|
|
Debug.assert(!!errorNode);
|
|
if (incompatibleStack.length) reportIncompatibleStack();
|
|
if (message.elidedInCompatabilityPyramid) return;
|
|
errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3);
|
|
}
|
|
|
|
function associateRelatedInfo(info: DiagnosticRelatedInformation) {
|
|
Debug.assert(!!errorInfo);
|
|
if (!relatedInfo) {
|
|
relatedInfo = [info];
|
|
}
|
|
else {
|
|
relatedInfo.push(info);
|
|
}
|
|
}
|
|
|
|
function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) {
|
|
if (incompatibleStack.length) reportIncompatibleStack();
|
|
const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target);
|
|
|
|
if (target.flags & TypeFlags.TypeParameter && target.immediateBaseConstraint !== undefined && isTypeAssignableTo(source, target.immediateBaseConstraint)) {
|
|
reportError(
|
|
Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2,
|
|
sourceType,
|
|
targetType,
|
|
typeToString(target.immediateBaseConstraint),
|
|
);
|
|
}
|
|
|
|
if (!message) {
|
|
if (relation === comparableRelation) {
|
|
message = Diagnostics.Type_0_is_not_comparable_to_type_1;
|
|
}
|
|
else if (sourceType === targetType) {
|
|
message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated;
|
|
}
|
|
else {
|
|
message = Diagnostics.Type_0_is_not_assignable_to_type_1;
|
|
}
|
|
}
|
|
|
|
reportError(message, sourceType, targetType);
|
|
}
|
|
|
|
function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) {
|
|
const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source);
|
|
const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target);
|
|
|
|
if ((globalStringType === source && stringType === target) ||
|
|
(globalNumberType === source && numberType === target) ||
|
|
(globalBooleanType === source && booleanType === target) ||
|
|
(getGlobalESSymbolType(/*reportErrors*/ false) === source && esSymbolType === target)) {
|
|
reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try and elaborate array and tuple errors. Returns false
|
|
* if we have found an elaboration, or we should ignore
|
|
* any other elaborations when relating the `source` and
|
|
* `target` types.
|
|
*/
|
|
function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean {
|
|
/**
|
|
* The spec for elaboration is:
|
|
* - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations.
|
|
* - If the source is a tuple then skip property elaborations if the target is an array or tuple.
|
|
* - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations.
|
|
* - If the source an array then skip property elaborations if the target is a tuple.
|
|
*/
|
|
if (isTupleType(source)) {
|
|
if (source.target.readonly && isMutableArrayOrTuple(target)) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target));
|
|
}
|
|
return false;
|
|
}
|
|
return isTupleType(target) || isArrayType(target);
|
|
}
|
|
if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target));
|
|
}
|
|
return false;
|
|
}
|
|
if (isTupleType(target)) {
|
|
return isArrayType(source);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Compare two types and return
|
|
* * Ternary.True if they are related with no assumptions,
|
|
* * Ternary.Maybe if they are related with assumptions of other relationships, or
|
|
* * Ternary.False if they are not related.
|
|
*/
|
|
function isRelatedTo(originalSource: Type, originalTarget: Type, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary {
|
|
// Normalize the source and target types: Turn fresh literal types into regular literal types,
|
|
// turn deferred type references into regular type references, simplify indexed access and
|
|
// conditional types, and resolve substitution types to either the substitution (on the source
|
|
// side) or the type variable (on the target side).
|
|
let source = getNormalizedType(originalSource, /*writing*/ false);
|
|
let target = getNormalizedType(originalTarget, /*writing*/ true);
|
|
|
|
if (source === target) return Ternary.True;
|
|
|
|
if (relation === identityRelation) {
|
|
return isIdenticalTo(source, target);
|
|
}
|
|
|
|
// Try to see if we're relating something like `Foo` -> `Bar | null | undefined`.
|
|
// If so, reporting the `null` and `undefined` in the type is hardly useful.
|
|
// First, see if we're even relating an object type to a union.
|
|
// Then see if the target is stripped down to a single non-union type.
|
|
// Note
|
|
// * We actually want to remove null and undefined naively here (rather than using getNonNullableType),
|
|
// since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable<T>`"
|
|
// when dealing with generics.
|
|
// * We also don't deal with primitive source types, since we already halt elaboration below.
|
|
if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object &&
|
|
(target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) {
|
|
const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable);
|
|
if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) {
|
|
if (source === nullStrippedTarget) return Ternary.True;
|
|
target = nullStrippedTarget;
|
|
}
|
|
}
|
|
|
|
if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
|
|
isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
|
|
|
|
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
|
|
const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral);
|
|
if (isPerformingExcessPropertyChecks) {
|
|
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
|
|
if (reportErrors) {
|
|
reportRelationError(headMessage, source, target);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
|
|
const isPerformingCommonPropertyChecks = relation !== comparableRelation && !(intersectionState & IntersectionState.Target) &&
|
|
source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType &&
|
|
target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) &&
|
|
(getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source));
|
|
if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) {
|
|
if (reportErrors) {
|
|
const calls = getSignaturesOfType(source, SignatureKind.Call);
|
|
const constructs = getSignaturesOfType(source, SignatureKind.Construct);
|
|
if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, /*reportErrors*/ false) ||
|
|
constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, /*reportErrors*/ false)) {
|
|
reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, typeToString(source), typeToString(target));
|
|
}
|
|
else {
|
|
reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target));
|
|
}
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
|
|
let result = Ternary.False;
|
|
const saveErrorInfo = captureErrorCalculationState();
|
|
|
|
// Note that these checks are specifically ordered to produce correct results. In particular,
|
|
// we need to deconstruct unions before intersections (because unions are always at the top),
|
|
// and we need to handle "each" relations before "some" relations for the same kind of type.
|
|
if (source.flags & TypeFlags.Union) {
|
|
result = relation === comparableRelation ?
|
|
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) :
|
|
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
|
|
}
|
|
else {
|
|
if (target.flags & TypeFlags.Union) {
|
|
result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
|
|
}
|
|
else if (target.flags & TypeFlags.Intersection) {
|
|
result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target);
|
|
if (result && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks)) {
|
|
// Validate against excess props using the original `source`
|
|
if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None)) {
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
}
|
|
else if (source.flags & TypeFlags.Intersection) {
|
|
// Check to see if any constituents of the intersection are immediately related to the target.
|
|
//
|
|
// Don't report errors though. Checking whether a constituent is related to the source is not actually
|
|
// useful and leads to some confusing error messages. Instead it is better to let the below checks
|
|
// take care of this, or to not elaborate at all. For instance,
|
|
//
|
|
// - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
|
|
//
|
|
// - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
|
|
// than to report that 'D' is not assignable to 'A' or 'B'.
|
|
//
|
|
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
|
|
// breaking the intersection apart.
|
|
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false, IntersectionState.Source);
|
|
}
|
|
if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) {
|
|
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState)) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
}
|
|
}
|
|
}
|
|
if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) {
|
|
// The combined constraint of an intersection type is the intersection of the constraints of
|
|
// the constituents. When an intersection type contains instantiable types with union type
|
|
// constraints, there are situations where we need to examine the combined constraint. One is
|
|
// when the target is a union type. Another is when the intersection contains types belonging
|
|
// to one of the disjoint domains. For example, given type variables T and U, each with the
|
|
// constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and
|
|
// we need to check this constraint against a union on the target side. Also, given a type
|
|
// variable V constrained to 'string | number', 'V & number' has a combined constraint of
|
|
// 'string & number | number & number' which reduces to just 'number'.
|
|
// This also handles type parameters, as a type parameter with a union constraint compared against a union
|
|
// needs to have its constraint hoisted into an intersection with said type parameter, this way
|
|
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
|
|
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
|
|
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (<IntersectionType>source).types: [source], !!(target.flags & TypeFlags.Union));
|
|
if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) {
|
|
if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
|
|
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
|
|
if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!result && reportErrors) {
|
|
source = originalSource.aliasSymbol ? originalSource : source;
|
|
target = originalTarget.aliasSymbol ? originalTarget : target;
|
|
let maybeSuppress = overrideNextErrorInfo > 0;
|
|
if (maybeSuppress) {
|
|
overrideNextErrorInfo--;
|
|
}
|
|
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
|
|
const currentError = errorInfo;
|
|
tryElaborateArrayLikeErrors(source, target, reportErrors);
|
|
if (errorInfo !== currentError) {
|
|
maybeSuppress = !!errorInfo;
|
|
}
|
|
}
|
|
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) {
|
|
tryElaborateErrorsForPrimitivesAndObjects(source, target);
|
|
}
|
|
else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) {
|
|
reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead);
|
|
}
|
|
else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) {
|
|
const targetTypes = (target as IntersectionType).types;
|
|
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode);
|
|
const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode);
|
|
if (intrinsicAttributes !== errorType && intrinsicClassAttributes !== errorType &&
|
|
(contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) {
|
|
// do not report top error
|
|
return result;
|
|
}
|
|
}
|
|
if (!headMessage && maybeSuppress) {
|
|
lastSkippedInfo = [source, target];
|
|
// Used by, eg, missing property checking to replace the top-level message with a more informative one
|
|
return result;
|
|
}
|
|
reportRelationError(headMessage, source, target);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function isIdenticalTo(source: Type, target: Type): Ternary {
|
|
const flags = source.flags & target.flags;
|
|
if (!(flags & TypeFlags.Substructure)) {
|
|
return Ternary.False;
|
|
}
|
|
if (flags & TypeFlags.UnionOrIntersection) {
|
|
let result = eachTypeRelatedToSomeType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target);
|
|
if (result) {
|
|
result &= eachTypeRelatedToSomeType(<UnionOrIntersectionType>target, <UnionOrIntersectionType>source);
|
|
}
|
|
return result;
|
|
}
|
|
return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None);
|
|
}
|
|
|
|
function getTypeOfPropertyInTypes(types: Type[], name: __String) {
|
|
const appendPropType = (propTypes: Type[] | undefined, type: Type) => {
|
|
type = getApparentType(type);
|
|
const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(<UnionOrIntersectionType>type, name) : getPropertyOfObjectType(type, name);
|
|
const propType = prop && getTypeOfSymbol(prop) || isNumericLiteralName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || undefinedType;
|
|
return append(propTypes, propType);
|
|
};
|
|
return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray);
|
|
}
|
|
|
|
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
|
|
if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) {
|
|
return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny
|
|
}
|
|
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
|
|
if ((relation === assignableRelation || relation === comparableRelation) &&
|
|
(isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) {
|
|
return false;
|
|
}
|
|
let reducedTarget = target;
|
|
let checkTypes: Type[] | undefined;
|
|
if (target.flags & TypeFlags.Union) {
|
|
reducedTarget = findMatchingDiscriminantType(source, <UnionType>target, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(<UnionType>target);
|
|
checkTypes = reducedTarget.flags & TypeFlags.Union ? (<UnionType>reducedTarget).types : [reducedTarget];
|
|
}
|
|
for (const prop of getPropertiesOfType(source)) {
|
|
if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) {
|
|
if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) {
|
|
if (reportErrors) {
|
|
// Report error in terms of object types in the target as those are the only ones
|
|
// we check in isKnownProperty.
|
|
const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget);
|
|
// We know *exactly* where things went wrong when comparing the types.
|
|
// Use this property as the error node as this will be more helpful in
|
|
// reasoning about what went wrong.
|
|
if (!errorNode) return Debug.fail();
|
|
if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) {
|
|
// JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal.
|
|
// However, using an object-literal error message will be very confusing to the users so we give different a message.
|
|
// TODO: Spelling suggestions for excess jsx attributes (needs new diagnostic messages)
|
|
if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) {
|
|
// Note that extraneous children (as in `<NoChild>extra</NoChild>`) don't pass this check,
|
|
// since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute.
|
|
errorNode = prop.valueDeclaration.name;
|
|
}
|
|
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(errorTarget));
|
|
}
|
|
else {
|
|
// use the property's value declaration if the property is assigned inside the literal itself
|
|
const objectLiteralDeclaration = source.symbol && firstOrUndefined(source.symbol.declarations);
|
|
let suggestion;
|
|
if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) {
|
|
const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike;
|
|
Debug.assertNode(propDeclaration, isObjectLiteralElementLike);
|
|
|
|
errorNode = propDeclaration;
|
|
|
|
const name = propDeclaration.name!;
|
|
if (isIdentifier(name)) {
|
|
suggestion = getSuggestionForNonexistentProperty(name, errorTarget);
|
|
}
|
|
}
|
|
if (suggestion !== undefined) {
|
|
reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2,
|
|
symbolToString(prop), typeToString(errorTarget), suggestion);
|
|
}
|
|
else {
|
|
reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
|
|
symbolToString(prop), typeToString(errorTarget));
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), reportErrors)) {
|
|
if (reportErrors) {
|
|
reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) {
|
|
return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration;
|
|
}
|
|
|
|
function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary {
|
|
let result = Ternary.True;
|
|
const sourceTypes = source.types;
|
|
for (const sourceType of sourceTypes) {
|
|
const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false);
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary {
|
|
const targetTypes = target.types;
|
|
if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) {
|
|
return Ternary.True;
|
|
}
|
|
for (const type of targetTypes) {
|
|
const related = isRelatedTo(source, type, /*reportErrors*/ false);
|
|
if (related) {
|
|
return related;
|
|
}
|
|
}
|
|
if (reportErrors) {
|
|
const bestMatchingType = getBestMatchingType(source, target, isRelatedTo);
|
|
isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
|
|
function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
|
let result = Ternary.True;
|
|
const targetTypes = target.types;
|
|
for (const targetType of targetTypes) {
|
|
const related = isRelatedTo(source, targetType, reportErrors, /*headMessage*/ undefined, intersectionState);
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
|
const sourceTypes = source.types;
|
|
if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) {
|
|
return Ternary.True;
|
|
}
|
|
const len = sourceTypes.length;
|
|
for (let i = 0; i < len; i++) {
|
|
const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState);
|
|
if (related) {
|
|
return related;
|
|
}
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
|
|
function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary {
|
|
let result = Ternary.True;
|
|
const sourceTypes = source.types;
|
|
for (const sourceType of sourceTypes) {
|
|
const related = isRelatedTo(sourceType, target, reportErrors);
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
|
if (sources.length !== targets.length && relation === identityRelation) {
|
|
return Ternary.False;
|
|
}
|
|
const length = sources.length <= targets.length ? sources.length : targets.length;
|
|
let result = Ternary.True;
|
|
for (let i = 0; i < length; i++) {
|
|
// When variance information isn't available we default to covariance. This happens
|
|
// in the process of computing variance information for recursive types and when
|
|
// comparing 'this' type arguments.
|
|
const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant;
|
|
const variance = varianceFlags & VarianceFlags.VarianceMask;
|
|
// We ignore arguments for independent type parameters (because they're never witnessed).
|
|
if (variance !== VarianceFlags.Independent) {
|
|
const s = sources[i];
|
|
const t = targets[i];
|
|
let related = Ternary.True;
|
|
if (varianceFlags & VarianceFlags.Unmeasurable) {
|
|
// Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_.
|
|
// We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by
|
|
// the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be)
|
|
related = relation === identityRelation ? isRelatedTo(s, t, /*reportErrors*/ false) : compareTypesIdentical(s, t);
|
|
}
|
|
else if (variance === VarianceFlags.Covariant) {
|
|
related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState);
|
|
}
|
|
else if (variance === VarianceFlags.Contravariant) {
|
|
related = isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, intersectionState);
|
|
}
|
|
else if (variance === VarianceFlags.Bivariant) {
|
|
// In the bivariant case we first compare contravariantly without reporting
|
|
// errors. Then, if that doesn't succeed, we compare covariantly with error
|
|
// reporting. Thus, error elaboration will be based on the the covariant check,
|
|
// which is generally easier to reason about.
|
|
related = isRelatedTo(t, s, /*reportErrors*/ false);
|
|
if (!related) {
|
|
related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState);
|
|
}
|
|
}
|
|
else {
|
|
// In the invariant case we first compare covariantly, and only when that
|
|
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
|
|
// will typically be based on the covariant check.
|
|
related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState);
|
|
if (related) {
|
|
related &= isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, intersectionState);
|
|
}
|
|
}
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Determine if possibly recursive types are related. First, check if the result is already available in the global cache.
|
|
// Second, check if we have already started a comparison of the given two types in which case we assume the result to be true.
|
|
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
|
|
// equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion
|
|
// and issue an error. Otherwise, actually compare the structure of the two types.
|
|
function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
|
if (overflow) {
|
|
return Ternary.False;
|
|
}
|
|
const id = getRelationKey(source, target, intersectionState, relation);
|
|
const entry = relation.get(id);
|
|
if (entry !== undefined) {
|
|
if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
|
|
// We are elaborating errors and the cached result is an unreported failure. The result will be reported
|
|
// as a failure, and should be updated as a reported failure by the bottom of this function.
|
|
}
|
|
else {
|
|
if (outofbandVarianceMarkerHandler) {
|
|
// We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component
|
|
const saved = entry & RelationComparisonResult.ReportsMask;
|
|
if (saved & RelationComparisonResult.ReportsUnmeasurable) {
|
|
instantiateType(source, makeFunctionTypeMapper(reportUnmeasurableMarkers));
|
|
}
|
|
if (saved & RelationComparisonResult.ReportsUnreliable) {
|
|
instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers));
|
|
}
|
|
}
|
|
return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
|
|
}
|
|
}
|
|
if (!maybeKeys) {
|
|
maybeKeys = [];
|
|
sourceStack = [];
|
|
targetStack = [];
|
|
}
|
|
else {
|
|
for (let i = 0; i < maybeCount; i++) {
|
|
// If source and target are already being compared, consider them related with assumptions
|
|
if (id === maybeKeys[i]) {
|
|
return Ternary.Maybe;
|
|
}
|
|
}
|
|
if (depth === 100) {
|
|
overflow = true;
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
const maybeStart = maybeCount;
|
|
maybeKeys[maybeCount] = id;
|
|
maybeCount++;
|
|
sourceStack[depth] = source;
|
|
targetStack[depth] = target;
|
|
depth++;
|
|
const saveExpandingFlags = expandingFlags;
|
|
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= ExpandingFlags.Source;
|
|
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= ExpandingFlags.Target;
|
|
let originalHandler: typeof outofbandVarianceMarkerHandler;
|
|
let propagatingVarianceFlags: RelationComparisonResult = 0;
|
|
if (outofbandVarianceMarkerHandler) {
|
|
originalHandler = outofbandVarianceMarkerHandler;
|
|
outofbandVarianceMarkerHandler = onlyUnreliable => {
|
|
propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable;
|
|
return originalHandler!(onlyUnreliable);
|
|
};
|
|
}
|
|
const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, intersectionState) : Ternary.Maybe;
|
|
if (outofbandVarianceMarkerHandler) {
|
|
outofbandVarianceMarkerHandler = originalHandler;
|
|
}
|
|
expandingFlags = saveExpandingFlags;
|
|
depth--;
|
|
if (result) {
|
|
if (result === Ternary.True || depth === 0) {
|
|
// If result is definitely true, record all maybe keys as having succeeded
|
|
for (let i = maybeStart; i < maybeCount; i++) {
|
|
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
|
|
}
|
|
maybeCount = maybeStart;
|
|
}
|
|
}
|
|
else {
|
|
// A false result goes straight into global cache (when something is false under
|
|
// assumptions it will also be false without assumptions)
|
|
relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags);
|
|
maybeCount = maybeStart;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
|
const flags = source.flags & target.flags;
|
|
if (relation === identityRelation && !(flags & TypeFlags.Object)) {
|
|
if (flags & TypeFlags.Index) {
|
|
return isRelatedTo((<IndexType>source).type, (<IndexType>target).type, /*reportErrors*/ false);
|
|
}
|
|
let result = Ternary.False;
|
|
if (flags & TypeFlags.IndexedAccess) {
|
|
if (result = isRelatedTo((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType, /*reportErrors*/ false)) {
|
|
if (result &= isRelatedTo((<IndexedAccessType>source).indexType, (<IndexedAccessType>target).indexType, /*reportErrors*/ false)) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
if (flags & TypeFlags.Conditional) {
|
|
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive) {
|
|
if (result = isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType, /*reportErrors*/ false)) {
|
|
if (result &= isRelatedTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType, /*reportErrors*/ false)) {
|
|
if (result &= isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
|
|
if (result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (flags & TypeFlags.Substitution) {
|
|
return isRelatedTo((<SubstitutionType>source).substitute, (<SubstitutionType>target).substitute, /*reportErrors*/ false);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
|
|
let result: Ternary;
|
|
let originalErrorInfo: DiagnosticMessageChain | undefined;
|
|
let varianceCheckFailed = false;
|
|
const saveErrorInfo = captureErrorCalculationState();
|
|
|
|
// We limit alias variance probing to only object and conditional types since their alias behavior
|
|
// is more predictable than other, interned types, which may or may not have an alias depending on
|
|
// the order in which things were checked.
|
|
if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
|
|
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
|
|
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
|
|
const variances = getAliasVariances(source.aliasSymbol);
|
|
if (variances === emptyArray) {
|
|
return Ternary.Maybe;
|
|
}
|
|
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState);
|
|
if (varianceResult !== undefined) {
|
|
return varianceResult;
|
|
}
|
|
}
|
|
|
|
if (target.flags & TypeFlags.TypeParameter) {
|
|
// A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q].
|
|
if (getObjectFlags(source) & ObjectFlags.Mapped && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(<MappedType>source))) {
|
|
if (!(getMappedTypeModifiers(<MappedType>source) & MappedTypeModifiers.IncludeOptional)) {
|
|
const templateType = getTemplateTypeFromMappedType(<MappedType>source);
|
|
const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(<MappedType>source));
|
|
if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (target.flags & TypeFlags.Index) {
|
|
// A keyof S is related to a keyof T if T is related to S.
|
|
if (source.flags & TypeFlags.Index) {
|
|
if (result = isRelatedTo((<IndexType>target).type, (<IndexType>source).type, /*reportErrors*/ false)) {
|
|
return result;
|
|
}
|
|
}
|
|
// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
|
|
// simplified form of T or, if T doesn't simplify, the constraint of T.
|
|
const constraint = getSimplifiedTypeOrConstraint((<IndexType>target).type);
|
|
if (constraint) {
|
|
// We require Ternary.True here such that circular constraints don't cause
|
|
// false positives. For example, given 'T extends { [K in keyof T]: string }',
|
|
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
|
|
// related to other types.
|
|
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
|
|
return Ternary.True;
|
|
}
|
|
}
|
|
}
|
|
else if (target.flags & TypeFlags.IndexedAccess) {
|
|
// A type S is related to a type T[K] if S is related to C, where C is the base
|
|
// constraint of T[K] for writing.
|
|
if (relation !== identityRelation) {
|
|
const objectType = (<IndexedAccessType>target).objectType;
|
|
const indexType = (<IndexedAccessType>target).indexType;
|
|
const baseObjectType = getBaseConstraintOfType(objectType) || objectType;
|
|
const baseIndexType = getBaseConstraintOfType(indexType) || indexType;
|
|
if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) {
|
|
const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0);
|
|
const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, /*accessNode*/ undefined, accessFlags);
|
|
if (constraint && (result = isRelatedTo(source, constraint, reportErrors))) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (isGenericMappedType(target)) {
|
|
// A source type T is related to a target type { [P in X]: T[P] }
|
|
const template = getTemplateTypeFromMappedType(target);
|
|
const modifiers = getMappedTypeModifiers(target);
|
|
if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) {
|
|
if (template.flags & TypeFlags.IndexedAccess && (<IndexedAccessType>template).objectType === source &&
|
|
(<IndexedAccessType>template).indexType === getTypeParameterFromMappedType(target)) {
|
|
return Ternary.True;
|
|
}
|
|
if (!isGenericMappedType(source)) {
|
|
const targetConstraint = getConstraintTypeFromMappedType(target);
|
|
const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true);
|
|
const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional;
|
|
const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined;
|
|
// A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X.
|
|
// A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X.
|
|
if (includeOptional
|
|
? !(filteredByApplicability!.flags & TypeFlags.Never)
|
|
: isRelatedTo(targetConstraint, sourceKeys)) {
|
|
const typeParameter = getTypeParameterFromMappedType(target);
|
|
const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter;
|
|
const indexedAccessType = getIndexedAccessType(source, indexingType);
|
|
const templateType = getTemplateTypeFromMappedType(target);
|
|
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
|
|
return result;
|
|
}
|
|
}
|
|
originalErrorInfo = errorInfo;
|
|
resetErrorInfo(saveErrorInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (source.flags & TypeFlags.TypeVariable) {
|
|
if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
|
|
// A type S[K] is related to a type T[J] if S is related to T and K is related to J.
|
|
if (result = isRelatedTo((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType, reportErrors)) {
|
|
result &= isRelatedTo((<IndexedAccessType>source).indexType, (<IndexedAccessType>target).indexType, reportErrors);
|
|
}
|
|
if (result) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
}
|
|
else {
|
|
const constraint = getConstraintOfType(<TypeVariable>source);
|
|
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
|
|
// A type variable with no constraint is not related to the non-primitive object type.
|
|
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
}
|
|
// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
|
|
else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
// slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
|
|
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, intersectionState)) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
else if (source.flags & TypeFlags.Index) {
|
|
if (result = isRelatedTo(keyofConstraintType, target, reportErrors)) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
}
|
|
else if (source.flags & TypeFlags.Conditional) {
|
|
if (target.flags & TypeFlags.Conditional) {
|
|
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
|
|
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
|
|
// and Y1 is related to Y2.
|
|
const sourceParams = (source as ConditionalType).root.inferTypeParameters;
|
|
let sourceExtends = (<ConditionalType>source).extendsType;
|
|
let mapper: TypeMapper | undefined;
|
|
if (sourceParams) {
|
|
// If the source has infer type parameters, we instantiate them in the context of the target
|
|
const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedTo);
|
|
inferTypes(ctx.inferences, (<ConditionalType>target).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
|
|
sourceExtends = instantiateType(sourceExtends, ctx.mapper);
|
|
mapper = ctx.mapper;
|
|
}
|
|
if (isTypeIdenticalTo(sourceExtends, (<ConditionalType>target).extendsType) &&
|
|
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
|
|
if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(<ConditionalType>source), mapper), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
|
|
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
|
|
}
|
|
if (result) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
const distributiveConstraint = getConstraintOfDistributiveConditionalType(<ConditionalType>source);
|
|
if (distributiveConstraint) {
|
|
if (result = isRelatedTo(distributiveConstraint, target, reportErrors)) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
}
|
|
const defaultConstraint = getDefaultConstraintOfConditionalType(<ConditionalType>source);
|
|
if (defaultConstraint) {
|
|
if (result = isRelatedTo(defaultConstraint, target, reportErrors)) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// An empty object type is related to any mapped type that includes a '?' modifier.
|
|
if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) {
|
|
return Ternary.True;
|
|
}
|
|
if (isGenericMappedType(target)) {
|
|
if (isGenericMappedType(source)) {
|
|
if (result = mappedTypeRelatedTo(source, target, reportErrors)) {
|
|
resetErrorInfo(saveErrorInfo);
|
|
return result;
|
|
}
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive);
|
|
if (relation !== identityRelation) {
|
|
source = getApparentType(source);
|
|
}
|
|
else if (isGenericMappedType(source)) {
|
|
return Ternary.False;
|
|
}
|
|
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target &&
|
|
!(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) {
|
|
// We have type references to the same generic type, and the type references are not marker
|
|
// type references (which are intended by be compared structurally). Obtain the variance
|
|
// information for the type parameters and relate the type arguments accordingly.
|
|
const variances = getVariances((<TypeReference>source).target);
|
|
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
|
|
// effectively means we measure variance only from type parameter occurrences that aren't nested in
|
|
// recursive instantiations of the generic type.
|
|
if (variances === emptyArray) {
|
|
return Ternary.Maybe;
|
|
}
|
|
const varianceResult = relateVariances(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), variances, intersectionState);
|
|
if (varianceResult !== undefined) {
|
|
return varianceResult;
|
|
}
|
|
}
|
|
else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) {
|
|
if (relation !== identityRelation) {
|
|
return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors);
|
|
}
|
|
else {
|
|
// By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple
|
|
// or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
// Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})`
|
|
// and not `{} <- fresh({}) <- {[idx: string]: any}`
|
|
else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) {
|
|
return Ternary.False;
|
|
}
|
|
// Even if relationship doesn't hold for unions, intersections, or generic type references,
|
|
// it may hold in a structural comparison.
|
|
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
|
|
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
|
|
// relates to X. Thus, we include intersection types on the source side here.
|
|
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
|
|
// Report structural errors only if we haven't reported any errors yet
|
|
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive;
|
|
result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState);
|
|
if (result) {
|
|
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors);
|
|
if (result) {
|
|
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors);
|
|
if (result) {
|
|
result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors, intersectionState);
|
|
if (result) {
|
|
result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors, intersectionState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (varianceCheckFailed && result) {
|
|
errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false
|
|
}
|
|
else if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
// If S is an object type and T is a discriminated union, S may be related to T if
|
|
// there exists a constituent of T for every combination of the discriminants of S
|
|
// with respect to T. We do not report errors here, as we will use the existing
|
|
// error result from checking each constituent of the union.
|
|
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Union) {
|
|
const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Substitution);
|
|
if (objectOnlyTarget.flags & TypeFlags.Union) {
|
|
const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Ternary.False;
|
|
|
|
function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) {
|
|
if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) {
|
|
return result;
|
|
}
|
|
if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) {
|
|
// If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we
|
|
// have to allow a structural fallback check
|
|
// We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially
|
|
// be assuming identity of the type parameter.
|
|
originalErrorInfo = undefined;
|
|
resetErrorInfo(saveErrorInfo);
|
|
return undefined;
|
|
}
|
|
const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances);
|
|
varianceCheckFailed = !allowStructuralFallback;
|
|
// The type arguments did not relate appropriately, but it may be because we have no variance
|
|
// information (in which case typeArgumentsRelatedTo defaulted to covariance for all type
|
|
// arguments). It might also be the case that the target type has a 'void' type argument for
|
|
// a covariant type parameter that is only used in return positions within the generic type
|
|
// (in which case any type argument is permitted on the source side). In those cases we proceed
|
|
// with a structural comparison. Otherwise, we know for certain the instantiations aren't
|
|
// related and we can return here.
|
|
if (variances !== emptyArray && !allowStructuralFallback) {
|
|
// In some cases generic types that are covariant in regular type checking mode become
|
|
// invariant in --strictFunctionTypes mode because one or more type parameters are used in
|
|
// both co- and contravariant positions. In order to make it easier to diagnose *why* such
|
|
// types are invariant, if any of the type parameters are invariant we reset the reported
|
|
// errors and instead force a structural comparison (which will include elaborations that
|
|
// reveal the reason).
|
|
// We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`,
|
|
// we can return `False` early here to skip calculating the structural error message we don't need.
|
|
if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) {
|
|
return Ternary.False;
|
|
}
|
|
// We remember the original error information so we can restore it in case the structural
|
|
// comparison unexpectedly succeeds. This can happen when the structural comparison result
|
|
// is a Ternary.Maybe for example caused by the recursion depth limiter.
|
|
originalErrorInfo = errorInfo;
|
|
resetErrorInfo(saveErrorInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
function reportUnmeasurableMarkers(p: TypeParameter) {
|
|
if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) {
|
|
outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
function reportUnreliableMarkers(p: TypeParameter) {
|
|
if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) {
|
|
outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
// A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is
|
|
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
|
|
// that S and T are contra-variant whereas X and Y are co-variant.
|
|
function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary {
|
|
const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) :
|
|
getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target));
|
|
if (modifiersRelated) {
|
|
let result: Ternary;
|
|
const targetConstraint = getConstraintTypeFromMappedType(target);
|
|
const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), makeFunctionTypeMapper(getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers));
|
|
if (result = isRelatedTo(targetConstraint, sourceConstraint, reportErrors)) {
|
|
const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]);
|
|
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors);
|
|
}
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
|
|
function typeRelatedToDiscriminatedType(source: Type, target: UnionType) {
|
|
// 1. Generate the combinations of discriminant properties & types 'source' can satisfy.
|
|
// a. If the number of combinations is above a set limit, the comparison is too complex.
|
|
// 2. Filter 'target' to the subset of types whose discriminants exist in the matrix.
|
|
// a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related.
|
|
// 3. For each type in the filtered 'target', determine if all non-discriminant properties of
|
|
// 'target' are related to a property in 'source'.
|
|
//
|
|
// NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts
|
|
// for examples.
|
|
|
|
const sourceProperties = getPropertiesOfType(source);
|
|
const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target);
|
|
if (!sourcePropertiesFiltered) return Ternary.False;
|
|
|
|
// Though we could compute the number of combinations as we generate
|
|
// the matrix, this would incur additional memory overhead due to
|
|
// array allocations. To reduce this overhead, we first compute
|
|
// the number of combinations to ensure we will not surpass our
|
|
// fixed limit before incurring the cost of any allocations:
|
|
let numCombinations = 1;
|
|
for (const sourceProperty of sourcePropertiesFiltered) {
|
|
numCombinations *= countTypes(getTypeOfSymbol(sourceProperty));
|
|
if (numCombinations > 25) {
|
|
// We've reached the complexity limit.
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
|
|
// Compute the set of types for each discriminant property.
|
|
const sourceDiscriminantTypes: Type[][] = new Array<Type[]>(sourcePropertiesFiltered.length);
|
|
const excludedProperties = createUnderscoreEscapedMap<true>();
|
|
for (let i = 0; i < sourcePropertiesFiltered.length; i++) {
|
|
const sourceProperty = sourcePropertiesFiltered[i];
|
|
const sourcePropertyType = getTypeOfSymbol(sourceProperty);
|
|
sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union
|
|
? (sourcePropertyType as UnionType).types
|
|
: [sourcePropertyType];
|
|
excludedProperties.set(sourceProperty.escapedName, true);
|
|
}
|
|
|
|
// Match each combination of the cartesian product of discriminant properties to one or more
|
|
// constituents of 'target'. If any combination does not have a match then 'source' is not relatable.
|
|
const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes);
|
|
const matchingTypes: Type[] = [];
|
|
for (const combination of discriminantCombinations) {
|
|
let hasMatch = false;
|
|
outer: for (const type of target.types) {
|
|
for (let i = 0; i < sourcePropertiesFiltered.length; i++) {
|
|
const sourceProperty = sourcePropertiesFiltered[i];
|
|
const targetProperty = getPropertyOfType(type, sourceProperty.escapedName);
|
|
if (!targetProperty) continue outer;
|
|
if (sourceProperty === targetProperty) continue;
|
|
// We compare the source property to the target in the context of a single discriminant type.
|
|
const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, IntersectionState.None);
|
|
// If the target property could not be found, or if the properties were not related,
|
|
// then this constituent is not a match.
|
|
if (!related) {
|
|
continue outer;
|
|
}
|
|
}
|
|
pushIfUnique(matchingTypes, type, equateValues);
|
|
hasMatch = true;
|
|
}
|
|
if (!hasMatch) {
|
|
// We failed to match any type for this combination.
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
|
|
// Compare the remaining non-discriminant properties of each match.
|
|
let result = Ternary.True;
|
|
for (const type of matchingTypes) {
|
|
result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None);
|
|
if (result) {
|
|
result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false);
|
|
if (result) {
|
|
result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false);
|
|
if (result) {
|
|
result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None);
|
|
if (result) {
|
|
result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!result) {
|
|
return result;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function excludeProperties(properties: Symbol[], excludedProperties: UnderscoreEscapedMap<true> | undefined) {
|
|
if (!excludedProperties || properties.length === 0) return properties;
|
|
let result: Symbol[] | undefined;
|
|
for (let i = 0; i < properties.length; i++) {
|
|
if (!excludedProperties.has(properties[i].escapedName)) {
|
|
if (result) {
|
|
result.push(properties[i]);
|
|
}
|
|
}
|
|
else if (!result) {
|
|
result = properties.slice(0, i);
|
|
}
|
|
}
|
|
return result || properties;
|
|
}
|
|
|
|
function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
|
const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial);
|
|
const source = getTypeOfSourceProperty(sourceProp);
|
|
if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) {
|
|
// Rather than resolving (and normalizing) the type, relate constituent-by-constituent without performing normalization or seconadary passes
|
|
const links = getSymbolLinks(targetProp);
|
|
Debug.assertIsDefined(links.deferralParent);
|
|
Debug.assertIsDefined(links.deferralConstituents);
|
|
const unionParent = !!(links.deferralParent.flags & TypeFlags.Union);
|
|
let result = unionParent ? Ternary.False : Ternary.True;
|
|
const targetTypes = links.deferralConstituents;
|
|
for (const targetType of targetTypes) {
|
|
const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, unionParent ? 0 : IntersectionState.Target);
|
|
if (!unionParent) {
|
|
if (!related) {
|
|
// Can't assign to a target individually - have to fallback to assigning to the _whole_ intersection (which forces normalization)
|
|
return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
|
|
}
|
|
result &= related;
|
|
}
|
|
else {
|
|
if (related) {
|
|
return related;
|
|
}
|
|
}
|
|
}
|
|
if (unionParent && !result && targetIsOptional) {
|
|
result = isRelatedTo(source, undefinedType);
|
|
}
|
|
if (unionParent && !result && reportErrors) {
|
|
// The easiest way to get the right errors here is to un-defer (which may be costly)
|
|
// If it turns out this is too costly too often, we can replicate the error handling logic within
|
|
// typeRelatedToSomeType without the discriminatable type branch (as that requires a manifest union
|
|
// type on which to hand discriminable properties, which we are expressly trying to avoid here)
|
|
return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
|
|
}
|
|
return result;
|
|
}
|
|
else {
|
|
return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors, /*headMessage*/ undefined, intersectionState);
|
|
}
|
|
}
|
|
|
|
function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
|
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
|
|
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
|
|
if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) {
|
|
const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration;
|
|
if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
if (hasDifferingDeclarations) {
|
|
if (reportErrors) {
|
|
if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) {
|
|
reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp));
|
|
}
|
|
else {
|
|
reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp),
|
|
typeToString(sourcePropFlags & ModifierFlags.Private ? source : target),
|
|
typeToString(sourcePropFlags & ModifierFlags.Private ? target : source));
|
|
}
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
else if (targetPropFlags & ModifierFlags.Protected) {
|
|
if (!isValidOverrideOf(sourceProp, targetProp)) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp),
|
|
typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
else if (sourcePropFlags & ModifierFlags.Protected) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2,
|
|
symbolToString(targetProp), typeToString(source), typeToString(target));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
// If the target comes from a partial union prop, allow `undefined` in the target type
|
|
const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState);
|
|
if (!related) {
|
|
if (reportErrors) {
|
|
reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
// When checking for comparability, be more lenient with optional properties.
|
|
if (relation !== comparableRelation && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
|
|
// TypeScript 1.0 spec (April 2014): 3.8.3
|
|
// S is a subtype of a type T, and T is a supertype of S if ...
|
|
// S' and T are object types and, for each member M in T..
|
|
// M is a property and S' contains a property N where
|
|
// if M is a required property, N is also a required property
|
|
// (M - property in T)
|
|
// (N - property in S)
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2,
|
|
symbolToString(targetProp), typeToString(source), typeToString(target));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
return related;
|
|
}
|
|
|
|
function reportUnmatchedProperty(source: Type, target: Type, unmatchedProperty: Symbol, requireOptionalProperties: boolean) {
|
|
let shouldSkipElaboration = false;
|
|
// give specific error in case where private names have the same description
|
|
if (unmatchedProperty.valueDeclaration
|
|
&& isNamedDeclaration(unmatchedProperty.valueDeclaration)
|
|
&& isPrivateIdentifier(unmatchedProperty.valueDeclaration.name)
|
|
&& source.symbol
|
|
&& source.symbol.flags & SymbolFlags.Class) {
|
|
const privateIdentifierDescription = unmatchedProperty.valueDeclaration.name.escapedText;
|
|
const symbolTableKey = getSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription);
|
|
if (symbolTableKey && getPropertyOfType(source, symbolTableKey)) {
|
|
const sourceName = getDeclarationName(source.symbol.valueDeclaration);
|
|
const targetName = getDeclarationName(target.symbol.valueDeclaration);
|
|
reportError(
|
|
Diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2,
|
|
diagnosticName(privateIdentifierDescription),
|
|
diagnosticName(sourceName.escapedText === "" ? anon : sourceName),
|
|
diagnosticName(targetName.escapedText === "" ? anon : targetName));
|
|
return;
|
|
}
|
|
}
|
|
const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false));
|
|
if (!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code &&
|
|
headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)) {
|
|
shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it
|
|
}
|
|
if (props.length === 1) {
|
|
const propName = symbolToString(unmatchedProperty);
|
|
reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target));
|
|
if (length(unmatchedProperty.declarations)) {
|
|
associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations[0], Diagnostics._0_is_declared_here, propName));
|
|
}
|
|
if (shouldSkipElaboration && errorInfo) {
|
|
overrideNextErrorInfo++;
|
|
}
|
|
}
|
|
else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) {
|
|
if (props.length > 5) { // arbitrary cutoff for too-long list form
|
|
reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4);
|
|
}
|
|
else {
|
|
reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", "));
|
|
}
|
|
if (shouldSkipElaboration && errorInfo) {
|
|
overrideNextErrorInfo++;
|
|
}
|
|
}
|
|
// No array like or unmatched property error - just issue top level error (errorInfo = undefined)
|
|
}
|
|
|
|
function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap<true> | undefined, intersectionState: IntersectionState): Ternary {
|
|
|
|
if (relation === identityRelation) {
|
|
return propertiesIdenticalTo(source, target, excludedProperties);
|
|
}
|
|
const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source);
|
|
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false);
|
|
if (unmatchedProperty) {
|
|
if (reportErrors) {
|
|
reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
if (isObjectLiteralType(target)) {
|
|
for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) {
|
|
if (!getPropertyOfObjectType(target, sourceProp.escapedName)) {
|
|
const sourceType = getTypeOfSymbol(sourceProp);
|
|
if (!(sourceType === undefinedType || sourceType === undefinedWideningType || sourceType === optionalType)) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let result = Ternary.True;
|
|
if (isTupleType(target)) {
|
|
const targetRestType = getRestTypeOfTupleType(target);
|
|
if (targetRestType) {
|
|
if (!isTupleType(source)) {
|
|
return Ternary.False;
|
|
}
|
|
const sourceRestType = getRestTypeOfTupleType(source);
|
|
if (sourceRestType && !isRelatedTo(sourceRestType, targetRestType, reportErrors)) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Rest_signatures_are_incompatible);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
const targetCount = getTypeReferenceArity(target) - 1;
|
|
const sourceCount = getTypeReferenceArity(source) - (sourceRestType ? 1 : 0);
|
|
const sourceTypeArguments = getTypeArguments(<TypeReference>source);
|
|
for (let i = targetCount; i < sourceCount; i++) {
|
|
const related = isRelatedTo(sourceTypeArguments[i], targetRestType, reportErrors);
|
|
if (!related) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Property_0_is_incompatible_with_rest_element_type, "" + i);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
}
|
|
}
|
|
// We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_
|
|
// from the target union, across all members
|
|
const properties = getPropertiesOfType(target);
|
|
const numericNamesOnly = isTupleType(source) && isTupleType(target);
|
|
for (const targetProp of excludeProperties(properties, excludedProperties)) {
|
|
const name = targetProp.escapedName;
|
|
if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) {
|
|
const sourceProp = getPropertyOfType(source, name);
|
|
if (sourceProp && sourceProp !== targetProp) {
|
|
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors, intersectionState);
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: UnderscoreEscapedMap<true> | undefined): Ternary {
|
|
if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) {
|
|
return Ternary.False;
|
|
}
|
|
const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties);
|
|
const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties);
|
|
if (sourceProperties.length !== targetProperties.length) {
|
|
return Ternary.False;
|
|
}
|
|
let result = Ternary.True;
|
|
for (const sourceProp of sourceProperties) {
|
|
const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName);
|
|
if (!targetProp) {
|
|
return Ternary.False;
|
|
}
|
|
const related = compareProperties(sourceProp, targetProp, isRelatedTo);
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean): Ternary {
|
|
if (relation === identityRelation) {
|
|
return signaturesIdenticalTo(source, target, kind);
|
|
}
|
|
if (target === anyFunctionType || source === anyFunctionType) {
|
|
return Ternary.True;
|
|
}
|
|
|
|
const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration);
|
|
const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration);
|
|
|
|
const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ?
|
|
SignatureKind.Call : kind);
|
|
const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ?
|
|
SignatureKind.Call : kind);
|
|
|
|
if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) {
|
|
if (isAbstractConstructorType(source) && !isAbstractConstructorType(target)) {
|
|
// An abstract constructor type is not assignable to a non-abstract constructor type
|
|
// as it would otherwise be possible to new an abstract class. Note that the assignability
|
|
// check we perform for an extends clause excludes construct signatures from the target,
|
|
// so this check never proceeds.
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) {
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
|
|
let result = Ternary.True;
|
|
const saveErrorInfo = captureErrorCalculationState();
|
|
const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn;
|
|
|
|
if (getObjectFlags(source) & ObjectFlags.Instantiated && getObjectFlags(target) & ObjectFlags.Instantiated && source.symbol === target.symbol) {
|
|
// We have instantiations of the same anonymous type (which typically will be the type of a
|
|
// method). Simply do a pairwise comparison of the signatures in the two signature lists instead
|
|
// of the much more expensive N * M comparison matrix we explore below. We erase type parameters
|
|
// as they are known to always be the same.
|
|
for (let i = 0; i < targetSignatures.length; i++) {
|
|
const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i]));
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
}
|
|
else if (sourceSignatures.length === 1 && targetSignatures.length === 1) {
|
|
// For simple functions (functions with a single signature) we only erase type parameters for
|
|
// the comparable relation. Otherwise, if the source signature is generic, we instantiate it
|
|
// in the context of the target signature before checking the relationship. Ideally we'd do
|
|
// this regardless of the number of signatures, but the potential costs are prohibitive due
|
|
// to the quadratic nature of the logic below.
|
|
const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks;
|
|
result = signatureRelatedTo(sourceSignatures[0], targetSignatures[0], eraseGenerics, reportErrors, incompatibleReporter(sourceSignatures[0], targetSignatures[0]));
|
|
}
|
|
else {
|
|
outer: for (const t of targetSignatures) {
|
|
// Only elaborate errors from the first failure
|
|
let shouldElaborateErrors = reportErrors;
|
|
for (const s of sourceSignatures) {
|
|
const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t));
|
|
if (related) {
|
|
result &= related;
|
|
resetErrorInfo(saveErrorInfo);
|
|
continue outer;
|
|
}
|
|
shouldElaborateErrors = false;
|
|
}
|
|
|
|
if (shouldElaborateErrors) {
|
|
reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1,
|
|
typeToString(source),
|
|
signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) {
|
|
if (siga.parameters.length === 0 && sigb.parameters.length === 0) {
|
|
return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target));
|
|
}
|
|
return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target));
|
|
}
|
|
|
|
function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) {
|
|
if (siga.parameters.length === 0 && sigb.parameters.length === 0) {
|
|
return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target));
|
|
}
|
|
return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target));
|
|
}
|
|
|
|
/**
|
|
* See signatureAssignableTo, compareSignaturesIdentical
|
|
*/
|
|
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
|
|
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target,
|
|
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, makeFunctionTypeMapper(reportUnreliableMarkers));
|
|
}
|
|
|
|
function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary {
|
|
const sourceSignatures = getSignaturesOfType(source, kind);
|
|
const targetSignatures = getSignaturesOfType(target, kind);
|
|
if (sourceSignatures.length !== targetSignatures.length) {
|
|
return Ternary.False;
|
|
}
|
|
let result = Ternary.True;
|
|
for (let i = 0; i < sourceSignatures.length; i++) {
|
|
const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo);
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary {
|
|
let result = Ternary.True;
|
|
const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(<IntersectionType>source) : getPropertiesOfObjectType(source);
|
|
for (const prop of props) {
|
|
// Skip over ignored JSX and symbol-named members
|
|
if (isIgnoredJsxProperty(source, prop)) {
|
|
continue;
|
|
}
|
|
const nameType = getSymbolLinks(prop).nameType;
|
|
if (nameType && nameType.flags & TypeFlags.UniqueESSymbol) {
|
|
continue;
|
|
}
|
|
if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) {
|
|
const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors);
|
|
if (!related) {
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function indexTypeRelatedTo(sourceType: Type, targetType: Type, reportErrors: boolean) {
|
|
const related = isRelatedTo(sourceType, targetType, reportErrors);
|
|
if (!related && reportErrors) {
|
|
reportError(Diagnostics.Index_signatures_are_incompatible);
|
|
}
|
|
return related;
|
|
}
|
|
|
|
function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
|
|
if (relation === identityRelation) {
|
|
return indexTypesIdenticalTo(source, target, kind);
|
|
}
|
|
const targetType = getIndexTypeOfType(target, kind);
|
|
if (!targetType || targetType.flags & TypeFlags.Any && !sourceIsPrimitive) {
|
|
// Index signature of type any permits assignment from everything but primitives
|
|
return Ternary.True;
|
|
}
|
|
if (isGenericMappedType(source)) {
|
|
// A generic mapped type { [P in K]: T } is related to an index signature { [x: string]: U }
|
|
// if T is related to U.
|
|
return kind === IndexKind.String ? isRelatedTo(getTemplateTypeFromMappedType(source), targetType, reportErrors) : Ternary.False;
|
|
}
|
|
const indexType = getIndexTypeOfType(source, kind) || kind === IndexKind.Number && getIndexTypeOfType(source, IndexKind.String);
|
|
if (indexType) {
|
|
return indexTypeRelatedTo(indexType, targetType, reportErrors);
|
|
}
|
|
if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) {
|
|
// Intersection constituents are never considered to have an inferred index signature
|
|
let related = eachPropertyRelatedTo(source, targetType, kind, reportErrors);
|
|
if (related && kind === IndexKind.String) {
|
|
const numberIndexType = getIndexTypeOfType(source, IndexKind.Number);
|
|
if (numberIndexType) {
|
|
related &= indexTypeRelatedTo(numberIndexType, targetType, reportErrors);
|
|
}
|
|
}
|
|
return related;
|
|
}
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source));
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
|
|
function indexTypesIdenticalTo(source: Type, target: Type, indexKind: IndexKind): Ternary {
|
|
const targetInfo = getIndexInfoOfType(target, indexKind);
|
|
const sourceInfo = getIndexInfoOfType(source, indexKind);
|
|
if (!sourceInfo && !targetInfo) {
|
|
return Ternary.True;
|
|
}
|
|
if (sourceInfo && targetInfo && sourceInfo.isReadonly === targetInfo.isReadonly) {
|
|
return isRelatedTo(sourceInfo.type, targetInfo.type);
|
|
}
|
|
return Ternary.False;
|
|
}
|
|
|
|
function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) {
|
|
if (!sourceSignature.declaration || !targetSignature.declaration) {
|
|
return true;
|
|
}
|
|
|
|
const sourceAccessibility = getSelectedModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier);
|
|
const targetAccessibility = getSelectedModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier);
|
|
|
|
// A public, protected and private signature is assignable to a private signature.
|
|
if (targetAccessibility === ModifierFlags.Private) {
|
|
return true;
|
|
}
|
|
|
|
// A public and protected signature is assignable to a protected signature.
|
|
if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) {
|
|
return true;
|
|
}
|
|
|
|
// Only a public signature is assignable to public signature.
|
|
if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) {
|
|
return true;
|
|
}
|
|
|
|
if (reportErrors) {
|
|
reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) {
|
|
return findMatchingDiscriminantType(source, target, isRelatedTo) ||
|
|
findMatchingTypeReferenceOrTypeAliasReference(source, target) ||
|
|
findBestTypeForObjectLiteral(source, target) ||
|
|
findBestTypeForInvokable(source, target) ||
|
|
findMostOverlappyType(source, target);
|
|
}
|
|
|
|
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary): Type | undefined;
|
|
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type): Type;
|
|
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type) {
|
|
// undefined=unknown, true=discriminated, false=not discriminated
|
|
// The state of each type progresses from left to right. Discriminated types stop at 'true'.
|
|
const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[];
|
|
for (const [getDiscriminatingType, propertyName] of discriminators) {
|
|
let i = 0;
|
|
for (const type of target.types) {
|
|
const targetType = getTypeOfPropertyOfType(type, propertyName);
|
|
if (targetType && related(getDiscriminatingType(), targetType)) {
|
|
discriminable[i] = discriminable[i] === undefined ? true : discriminable[i];
|
|
}
|
|
else {
|
|
discriminable[i] = false;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
const match = discriminable.indexOf(/*searchElement*/ true);
|
|
// make sure exactly 1 matches before returning it
|
|
return match === -1 || discriminable.indexOf(/*searchElement*/ true, match + 1) !== -1 ? defaultValue : target.types[match];
|
|
}
|
|
|
|
/**
|
|
* A type is 'weak' if it is an object type with at least one optional property
|
|
* and no required properties, call/construct signatures or index signatures
|
|
*/
|
|
function isWeakType(type: Type): boolean {
|
|
if (type.flags & TypeFlags.Object) {
|
|
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
|
|
return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 &&
|
|
!resolved.stringIndexInfo && !resolved.numberIndexInfo &&
|
|
resolved.properties.length > 0 &&
|
|
every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional));
|
|
}
|
|
if (type.flags & TypeFlags.Intersection) {
|
|
return every((<IntersectionType>type).types, isWeakType);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) {
|
|
for (const prop of getPropertiesOfType(source)) {
|
|
if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Return a type reference where the source type parameter is replaced with the target marker
|
|
// type, and flag the result as a marker type reference.
|
|
function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) {
|
|
const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t));
|
|
result.objectFlags |= ObjectFlags.MarkerType;
|
|
return result;
|
|
}
|
|
|
|
function getAliasVariances(symbol: Symbol) {
|
|
const links = getSymbolLinks(symbol);
|
|
return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => {
|
|
const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker)));
|
|
type.aliasTypeArgumentsContainsMarker = true;
|
|
return type;
|
|
});
|
|
}
|
|
|
|
// Return an array containing the variance of each type parameter. The variance is effectively
|
|
// a digest of the type comparisons that occur for each type argument when instantiations of the
|
|
// generic type are structurally compared. We infer the variance information by comparing
|
|
// instantiations of the generic type for type arguments with known relations. The function
|
|
// returns the emptyArray singleton when invoked recursively for the given generic type.
|
|
function getVariancesWorker<TCache extends { variances?: VarianceFlags[] }>(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] {
|
|
let variances = cache.variances;
|
|
if (!variances) {
|
|
// The emptyArray singleton is used to signal a recursive invocation.
|
|
cache.variances = emptyArray;
|
|
variances = [];
|
|
for (const tp of typeParameters) {
|
|
let unmeasurable = false;
|
|
let unreliable = false;
|
|
const oldHandler = outofbandVarianceMarkerHandler;
|
|
outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true;
|
|
// We first compare instantiations where the type parameter is replaced with
|
|
// marker types that have a known subtype relationship. From this we can infer
|
|
// invariance, covariance, contravariance or bivariance.
|
|
const typeWithSuper = createMarkerType(cache, tp, markerSuperType);
|
|
const typeWithSub = createMarkerType(cache, tp, markerSubType);
|
|
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) |
|
|
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0);
|
|
// If the instantiations appear to be related bivariantly it may be because the
|
|
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
|
|
// type). To determine this we compare instantiations where the type parameter is
|
|
// replaced with marker types that are known to be unrelated.
|
|
if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
|
|
variance = VarianceFlags.Independent;
|
|
}
|
|
outofbandVarianceMarkerHandler = oldHandler;
|
|
if (unmeasurable || unreliable) {
|
|
if (unmeasurable) {
|
|
variance |= VarianceFlags.Unmeasurable;
|
|
}
|
|
if (unreliable) {
|
|
variance |= VarianceFlags.Unreliable;
|
|
}
|
|
}
|
|
variances.push(variance);
|
|
}
|
|
cache.variances = variances;
|
|
}
|
|
return variances;
|
|
}
|
|
|
|
function getVariances(type: GenericType): VarianceFlags[] {
|
|
// Arrays and tuples are known to be covariant, no need to spend time computing this.
|
|
if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) {
|
|
return arrayVariances;
|
|
}
|
|
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference);
|
|
}
|
|
|
|
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
|
|
// See comment at call in recursiveTypeRelatedTo for when this case matters.
|
|
function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean {
|
|
for (let i = 0; i < variances.length; i++) {
|
|
if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isUnconstrainedTypeParameter(type: Type) {
|
|
return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter(<TypeParameter>type);
|
|
}
|
|
|
|
function isNonDeferredTypeReference(type: Type): type is TypeReference {
|
|
return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(<TypeReference>type).node;
|
|
}
|
|
|
|
function isTypeReferenceWithGenericArguments(type: Type): boolean {
|
|
return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => isUnconstrainedTypeParameter(t) || isTypeReferenceWithGenericArguments(t));
|
|
}
|
|
|
|
/**
|
|
* getTypeReferenceId(A<T, number, U>) returns "111=0-12=1"
|
|
* where A.id=111 and number.id=12
|
|
*/
|
|
function getTypeReferenceId(type: TypeReference, typeParameters: Type[], depth = 0) {
|
|
let result = "" + type.target.id;
|
|
for (const t of getTypeArguments(type)) {
|
|
if (isUnconstrainedTypeParameter(t)) {
|
|
let index = typeParameters.indexOf(t);
|
|
if (index < 0) {
|
|
index = typeParameters.length;
|
|
typeParameters.push(t);
|
|
}
|
|
result += "=" + index;
|
|
}
|
|
else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) {
|
|
result += "<" + getTypeReferenceId(t as TypeReference, typeParameters, depth + 1) + ">";
|
|
}
|
|
else {
|
|
result += "-" + t.id;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters.
|
|
* For other cases, the types ids are used.
|
|
*/
|
|
function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: Map<RelationComparisonResult>) {
|
|
if (relation === identityRelation && source.id > target.id) {
|
|
const temp = source;
|
|
source = target;
|
|
target = temp;
|
|
}
|
|
const postFix = intersectionState ? ":" + intersectionState : "";
|
|
if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
|
|
const typeParameters: Type[] = [];
|
|
return getTypeReferenceId(<TypeReference>source, typeParameters) + "," + getTypeReferenceId(<TypeReference>target, typeParameters) + postFix;
|
|
}
|
|
return source.id + "," + target.id + postFix;
|
|
}
|
|
|
|
// Invoke the callback for each underlying property symbol of the given symbol and return the first
|
|
// value that isn't undefined.
|
|
function forEachProperty<T>(prop: Symbol, callback: (p: Symbol) => T): T | undefined {
|
|
if (getCheckFlags(prop) & CheckFlags.Synthetic) {
|
|
for (const t of (<TransientSymbol>prop).containingType!.types) {
|
|
const p = getPropertyOfType(t, prop.escapedName);
|
|
const result = p && forEachProperty(p, callback);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
return callback(prop);
|
|
}
|
|
|
|
// Return the declaring class type of a property or undefined if property not declared in class
|
|
function getDeclaringClass(prop: Symbol) {
|
|
return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) : undefined;
|
|
}
|
|
|
|
// Return true if some underlying source property is declared in a class that derives
|
|
// from the given base class.
|
|
function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) {
|
|
return forEachProperty(prop, sp => {
|
|
const sourceClass = getDeclaringClass(sp);
|
|
return sourceClass ? hasBaseType(sourceClass, baseClass) : false;
|
|
});
|
|
}
|
|
|
|
// Return true if source property is a valid override of protected parts of target property.
|
|
function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) {
|
|
return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ?
|
|
!isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false);
|
|
}
|
|
|
|
// Return true if the given class derives from each of the declaring classes of the protected
|
|
// constituents of the given property.
|
|
function isClassDerivedFromDeclaringClasses(checkClass: Type, prop: Symbol) {
|
|
return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.Protected ?
|
|
!hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass;
|
|
}
|
|
|
|
// Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons
|
|
// for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible,
|
|
// though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely
|
|
// expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5
|
|
// levels, but unequal at some level beyond that.
|
|
// In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially
|
|
// the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives
|
|
// for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
|
|
function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean {
|
|
// We track all object types that have an associated symbol (representing the origin of the type)
|
|
if (depth >= 5 && type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) {
|
|
const symbol = type.symbol;
|
|
if (symbol) {
|
|
let count = 0;
|
|
for (let i = 0; i < depth; i++) {
|
|
const t = stack[i];
|
|
if (t.flags & TypeFlags.Object && t.symbol === symbol) {
|
|
count++;
|
|
if (count >= 5) return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (depth >= 5 && type.flags & TypeFlags.IndexedAccess) {
|
|
const root = getRootObjectTypeFromIndexedAccessChain(type);
|
|
let count = 0;
|
|
for (let i = 0; i < depth; i++) {
|
|
const t = stack[i];
|
|
if (getRootObjectTypeFromIndexedAccessChain(t) === root) {
|
|
count++;
|
|
if (count >= 5) return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the leftmost object type in a chain of indexed accesses, eg, in A[P][Q], returns A
|
|
*/
|
|
function getRootObjectTypeFromIndexedAccessChain(type: Type) {
|
|
let t = type;
|
|
while (t.flags & TypeFlags.IndexedAccess) {
|
|
t = (t as IndexedAccessType).objectType;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
|
|
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
|
|
}
|
|
|
|
function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary {
|
|
// Two members are considered identical when
|
|
// - they are public properties with identical names, optionality, and types,
|
|
// - they are private or protected properties originating in the same declaration and having identical types
|
|
if (sourceProp === targetProp) {
|
|
return Ternary.True;
|
|
}
|
|
const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier;
|
|
const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier;
|
|
if (sourcePropAccessibility !== targetPropAccessibility) {
|
|
return Ternary.False;
|
|
}
|
|
if (sourcePropAccessibility) {
|
|
if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) {
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
else {
|
|
if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) {
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) {
|
|
return Ternary.False;
|
|
}
|
|
return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp));
|
|
}
|
|
|
|
function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) {
|
|
const sourceParameterCount = getParameterCount(source);
|
|
const targetParameterCount = getParameterCount(target);
|
|
const sourceMinArgumentCount = getMinArgumentCount(source);
|
|
const targetMinArgumentCount = getMinArgumentCount(target);
|
|
const sourceHasRestParameter = hasEffectiveRestParameter(source);
|
|
const targetHasRestParameter = hasEffectiveRestParameter(target);
|
|
// A source signature matches a target signature if the two signatures have the same number of required,
|
|
// optional, and rest parameters.
|
|
if (sourceParameterCount === targetParameterCount &&
|
|
sourceMinArgumentCount === targetMinArgumentCount &&
|
|
sourceHasRestParameter === targetHasRestParameter) {
|
|
return true;
|
|
}
|
|
// A source signature partially matches a target signature if the target signature has no fewer required
|
|
// parameters
|
|
if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* See signatureRelatedTo, compareSignaturesIdentical
|
|
*/
|
|
function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary {
|
|
// TODO (drosen): De-duplicate code between related functions.
|
|
if (source === target) {
|
|
return Ternary.True;
|
|
}
|
|
if (!(isMatchingSignature(source, target, partialMatch))) {
|
|
return Ternary.False;
|
|
}
|
|
// Check that the two signatures have the same number of type parameters.
|
|
if (length(source.typeParameters) !== length(target.typeParameters)) {
|
|
return Ternary.False;
|
|
}
|
|
// Check that type parameter constraints and defaults match. If they do, instantiate the source
|
|
// signature with the type parameters of the target signature and continue the comparison.
|
|
if (target.typeParameters) {
|
|
const mapper = createTypeMapper(source.typeParameters!, target.typeParameters);
|
|
for (let i = 0; i < target.typeParameters.length; i++) {
|
|
const s = source.typeParameters![i];
|
|
const t = target.typeParameters[i];
|
|
if (!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) &&
|
|
compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))) {
|
|
return Ternary.False;
|
|
}
|
|
}
|
|
source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true);
|
|
}
|
|
let result = Ternary.True;
|
|
if (!ignoreThisTypes) {
|
|
const sourceThisType = getThisTypeOfSignature(source);
|
|
if (sourceThisType) {
|
|
const targetThisType = getThisTypeOfSignature(target);
|
|
if (targetThisType) {
|
|
const related = compareTypes(sourceThisType, targetThisType);
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
}
|
|
}
|
|
const targetLen = getParameterCount(target);
|
|
for (let i = 0; i < targetLen; i++) {
|
|
const s = getTypeAtPosition(source, i);
|
|
const t = getTypeAtPosition(target, i);
|
|
const related = compareTypes(t, s);
|
|
if (!related) {
|
|
return Ternary.False;
|
|
}
|
|
result &= related;
|
|
}
|
|
if (!ignoreReturnTypes) {
|
|
const sourceTypePredicate = getTypePredicateOfSignature(source);
|
|
const targetTypePredicate = getTypePredicateOfSignature(target);
|
|
result &= sourceTypePredicate || targetTypePredicate ?
|
|
compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) :
|
|
compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary {
|
|
return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False :
|
|
source.type === target.type ? Ternary.True :
|
|
source.type && target.type ? compareTypes(source.type, target.type) :
|
|
Ternary.False;
|
|
}
|
|
|
|
function literalTypesWithSameBaseType(types: Type[]): boolean {
|
|
let commonBaseType: Type | undefined;
|
|
for (const t of types) {
|
|
const baseType = getBaseTypeOfLiteralType(t);
|
|
if (!commonBaseType) {
|
|
commonBaseType = baseType;
|
|
}
|
|
if (baseType === t || baseType !== commonBaseType) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// When the candidate types are all literal types with the same base type, return a union
|
|
// of those literal types. Otherwise, return the leftmost type for which no type to the
|
|
// right is a supertype.
|
|
function getSupertypeOrUnion(types: Type[]): Type {
|
|
return literalTypesWithSameBaseType(types) ?
|
|
getUnionType(types) :
|
|
reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!;
|
|
}
|
|
|
|
function getCommonSupertype(types: Type[]): Type {
|
|
if (!strictNullChecks) {
|
|
return getSupertypeOrUnion(types);
|
|
}
|
|
const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable));
|
|
return primaryTypes.length ?
|
|
getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) :
|
|
getUnionType(types, UnionReduction.Subtype);
|
|
}
|
|
|
|
// Return the leftmost type for which no type to the right is a subtype.
|
|
function getCommonSubtype(types: Type[]) {
|
|
return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!;
|
|
}
|
|
|
|
function isArrayType(type: Type): boolean {
|
|
return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((<TypeReference>type).target === globalArrayType || (<TypeReference>type).target === globalReadonlyArrayType);
|
|
}
|
|
|
|
function isReadonlyArrayType(type: Type): boolean {
|
|
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalReadonlyArrayType;
|
|
}
|
|
|
|
function isMutableArrayOrTuple(type: Type): boolean {
|
|
return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly;
|
|
}
|
|
|
|
function getElementTypeOfArrayType(type: Type): Type | undefined {
|
|
return isArrayType(type) ? getTypeArguments(type as TypeReference)[0] : undefined;
|
|
}
|
|
|
|
function isArrayLikeType(type: Type): boolean {
|
|
// A type is array-like if it is a reference to the global Array or global ReadonlyArray type,
|
|
// or if it is not the undefined or null type and if it is assignable to ReadonlyArray<any>
|
|
return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
|
|
}
|
|
|
|
function isEmptyArrayLiteralType(type: Type): boolean {
|
|
const elementType = isArrayType(type) ? getTypeArguments(<TypeReference>type)[0] : undefined;
|
|
return elementType === undefinedWideningType || elementType === implicitNeverType;
|
|
}
|
|
|
|
function isTupleLikeType(type: Type): boolean {
|
|
return isTupleType(type) || !!getPropertyOfType(type, "0" as __String);
|
|
}
|
|
|
|
function isArrayOrTupleLikeType(type: Type): boolean {
|
|
return isArrayLikeType(type) || isTupleLikeType(type);
|
|
}
|
|
|
|
function getTupleElementType(type: Type, index: number) {
|
|
const propType = getTypeOfPropertyOfType(type, "" + index as __String);
|
|
if (propType) {
|
|
return propType;
|
|
}
|
|
if (everyType(type, isTupleType)) {
|
|
return mapType(type, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function isNeitherUnitTypeNorNever(type: Type): boolean {
|
|
return !(type.flags & (TypeFlags.Unit | TypeFlags.Never));
|
|
}
|
|
|
|
function isUnitType(type: Type): boolean {
|
|
return !!(type.flags & TypeFlags.Unit);
|
|
}
|
|
|
|
function isLiteralType(type: Type): boolean {
|
|
return type.flags & TypeFlags.Boolean ? true :
|
|
type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((<UnionType>type).types, isUnitType) :
|
|
isUnitType(type);
|
|
}
|
|
|
|
function getBaseTypeOfLiteralType(type: Type): Type {
|
|
return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
|
|
type.flags & TypeFlags.StringLiteral ? stringType :
|
|
type.flags & TypeFlags.NumberLiteral ? numberType :
|
|
type.flags & TypeFlags.BigIntLiteral ? bigintType :
|
|
type.flags & TypeFlags.BooleanLiteral ? booleanType :
|
|
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getBaseTypeOfLiteralType)) :
|
|
type;
|
|
}
|
|
|
|
function getWidenedLiteralType(type: Type): Type {
|
|
return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
|
|
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
|
|
type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType :
|
|
type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType :
|
|
type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType :
|
|
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getWidenedLiteralType)) :
|
|
type;
|
|
}
|
|
|
|
function getWidenedUniqueESSymbolType(type: Type): Type {
|
|
return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType :
|
|
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getWidenedUniqueESSymbolType)) :
|
|
type;
|
|
}
|
|
|
|
function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) {
|
|
if (!isLiteralOfContextualType(type, contextualType)) {
|
|
type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type));
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) {
|
|
if (type && isUnitType(type)) {
|
|
const contextualType = !contextualSignatureReturnType ? undefined :
|
|
isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) :
|
|
contextualSignatureReturnType;
|
|
type = getWidenedLiteralLikeTypeForContextualType(type, contextualType);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) {
|
|
if (type && isUnitType(type)) {
|
|
const contextualType = !contextualSignatureReturnType ? undefined :
|
|
getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator);
|
|
type = getWidenedLiteralLikeTypeForContextualType(type, contextualType);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Check if a Type was written as a tuple type literal.
|
|
* Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required.
|
|
*/
|
|
function isTupleType(type: Type): type is TupleTypeReference {
|
|
return !!(getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).target.objectFlags & ObjectFlags.Tuple);
|
|
}
|
|
|
|
function getRestTypeOfTupleType(type: TupleTypeReference) {
|
|
return type.target.hasRestElement ? getTypeArguments(type)[type.target.typeParameters!.length - 1] : undefined;
|
|
}
|
|
|
|
function getRestArrayTypeOfTupleType(type: TupleTypeReference) {
|
|
const restType = getRestTypeOfTupleType(type);
|
|
return restType && createArrayType(restType);
|
|
}
|
|
|
|
function getLengthOfTupleType(type: TupleTypeReference) {
|
|
return getTypeReferenceArity(type) - (type.target.hasRestElement ? 1 : 0);
|
|
}
|
|
|
|
function isZeroBigInt({value}: BigIntLiteralType) {
|
|
return value.base10Value === "0";
|
|
}
|
|
|
|
function getFalsyFlagsOfTypes(types: Type[]): TypeFlags {
|
|
let result: TypeFlags = 0;
|
|
for (const t of types) {
|
|
result |= getFalsyFlags(t);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null
|
|
// flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns
|
|
// no flags for all other types (including non-falsy literal types).
|
|
function getFalsyFlags(type: Type): TypeFlags {
|
|
return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((<UnionType>type).types) :
|
|
type.flags & TypeFlags.StringLiteral ? (<StringLiteralType>type).value === "" ? TypeFlags.StringLiteral : 0 :
|
|
type.flags & TypeFlags.NumberLiteral ? (<NumberLiteralType>type).value === 0 ? TypeFlags.NumberLiteral : 0 :
|
|
type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(<BigIntLiteralType>type) ? TypeFlags.BigIntLiteral : 0 :
|
|
type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 :
|
|
type.flags & TypeFlags.PossiblyFalsy;
|
|
}
|
|
|
|
function removeDefinitelyFalsyTypes(type: Type): Type {
|
|
return getFalsyFlags(type) & TypeFlags.DefinitelyFalsy ?
|
|
filterType(type, t => !(getFalsyFlags(t) & TypeFlags.DefinitelyFalsy)) :
|
|
type;
|
|
}
|
|
|
|
function extractDefinitelyFalsyTypes(type: Type): Type {
|
|
return mapType(type, getDefinitelyFalsyPartOfType);
|
|
}
|
|
|
|
function getDefinitelyFalsyPartOfType(type: Type): Type {
|
|
return type.flags & TypeFlags.String ? emptyStringType :
|
|
type.flags & TypeFlags.Number ? zeroType :
|
|
type.flags & TypeFlags.BigInt ? zeroBigIntType :
|
|
type === regularFalseType ||
|
|
type === falseType ||
|
|
type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null) ||
|
|
type.flags & TypeFlags.StringLiteral && (<StringLiteralType>type).value === "" ||
|
|
type.flags & TypeFlags.NumberLiteral && (<NumberLiteralType>type).value === 0 ||
|
|
type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(<BigIntLiteralType>type) ? type :
|
|
neverType;
|
|
}
|
|
|
|
/**
|
|
* Add undefined or null or both to a type if they are missing.
|
|
* @param type - type to add undefined and/or null to if not present
|
|
* @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both
|
|
*/
|
|
function getNullableType(type: Type, flags: TypeFlags): Type {
|
|
const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null);
|
|
return missing === 0 ? type :
|
|
missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) :
|
|
missing === TypeFlags.Null ? getUnionType([type, nullType]) :
|
|
getUnionType([type, undefinedType, nullType]);
|
|
}
|
|
|
|
function getOptionalType(type: Type): Type {
|
|
Debug.assert(strictNullChecks);
|
|
return type.flags & TypeFlags.Undefined ? type : getUnionType([type, undefinedType]);
|
|
}
|
|
|
|
function getGlobalNonNullableTypeInstantiation(type: Type) {
|
|
if (!deferredGlobalNonNullableTypeAlias) {
|
|
deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol;
|
|
}
|
|
// Use NonNullable global type alias if available to improve quick info/declaration emit
|
|
if (deferredGlobalNonNullableTypeAlias !== unknownSymbol) {
|
|
return getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]);
|
|
}
|
|
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); // Type alias unavailable, fall back to non-higher-order behavior
|
|
}
|
|
|
|
function getNonNullableType(type: Type): Type {
|
|
return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : type;
|
|
}
|
|
|
|
function addOptionalTypeMarker(type: Type) {
|
|
return strictNullChecks ? getUnionType([type, optionalType]) : type;
|
|
}
|
|
|
|
function isNotOptionalTypeMarker(type: Type) {
|
|
return type !== optionalType;
|
|
}
|
|
|
|
function removeOptionalTypeMarker(type: Type): Type {
|
|
return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type;
|
|
}
|
|
|
|
function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) {
|
|
return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type;
|
|
}
|
|
|
|
function getOptionalExpressionType(exprType: Type, expression: Expression) {
|
|
return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) :
|
|
isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) :
|
|
exprType;
|
|
}
|
|
|
|
/**
|
|
* Is source potentially coercible to target type under `==`.
|
|
* Assumes that `source` is a constituent of a union, hence
|
|
* the boolean literal flag on the LHS, but not on the RHS.
|
|
*
|
|
* This does not fully replicate the semantics of `==`. The
|
|
* intention is to catch cases that are clearly not right.
|
|
*
|
|
* Comparing (string | number) to number should not remove the
|
|
* string element.
|
|
*
|
|
* Comparing (string | number) to 1 will remove the string
|
|
* element, though this is not sound. This is a pragmatic
|
|
* choice.
|
|
*
|
|
* @see narrowTypeByEquality
|
|
*
|
|
* @param source
|
|
* @param target
|
|
*/
|
|
function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean {
|
|
return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0)
|
|
&& ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0);
|
|
}
|
|
|
|
/**
|
|
* Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module
|
|
* with no call or construct signatures.
|
|
*/
|
|
function isObjectTypeWithInferableIndex(type: Type): boolean {
|
|
return type.flags & TypeFlags.Intersection ? every((<IntersectionType>type).types, isObjectTypeWithInferableIndex) :
|
|
!!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 &&
|
|
!typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source));
|
|
}
|
|
|
|
function createSymbolWithType(source: Symbol, type: Type | undefined) {
|
|
const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly);
|
|
symbol.declarations = source.declarations;
|
|
symbol.parent = source.parent;
|
|
symbol.type = type;
|
|
symbol.target = source;
|
|
if (source.valueDeclaration) {
|
|
symbol.valueDeclaration = source.valueDeclaration;
|
|
}
|
|
const nameType = getSymbolLinks(source).nameType;
|
|
if (nameType) {
|
|
symbol.nameType = nameType;
|
|
}
|
|
return symbol;
|
|
}
|
|
|
|
function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) {
|
|
const members = createSymbolTable();
|
|
for (const property of getPropertiesOfObjectType(type)) {
|
|
const original = getTypeOfSymbol(property);
|
|
const updated = f(original);
|
|
members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated));
|
|
}
|
|
return members;
|
|
}
|
|
|
|
/**
|
|
* If the the provided object literal is subject to the excess properties check,
|
|
* create a new that is exempt. Recursively mark object literal members as exempt.
|
|
* Leave signatures alone since they are not subject to the check.
|
|
*/
|
|
function getRegularTypeOfObjectLiteral(type: Type): Type {
|
|
if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) {
|
|
return type;
|
|
}
|
|
const regularType = (<FreshObjectLiteralType>type).regularType;
|
|
if (regularType) {
|
|
return regularType;
|
|
}
|
|
|
|
const resolved = <ResolvedType>type;
|
|
const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral);
|
|
const regularNew = createAnonymousType(resolved.symbol,
|
|
members,
|
|
resolved.callSignatures,
|
|
resolved.constructSignatures,
|
|
resolved.stringIndexInfo,
|
|
resolved.numberIndexInfo);
|
|
regularNew.flags = resolved.flags;
|
|
regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral;
|
|
(<FreshObjectLiteralType>type).regularType = regularNew;
|
|
return regularNew;
|
|
}
|
|
|
|
function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext {
|
|
return { parent, propertyName, siblings, resolvedProperties: undefined };
|
|
}
|
|
|
|
function getSiblingsOfContext(context: WideningContext): Type[] {
|
|
if (!context.siblings) {
|
|
const siblings: Type[] = [];
|
|
for (const type of getSiblingsOfContext(context.parent!)) {
|
|
if (isObjectLiteralType(type)) {
|
|
const prop = getPropertyOfObjectType(type, context.propertyName!);
|
|
if (prop) {
|
|
forEachType(getTypeOfSymbol(prop), t => {
|
|
siblings.push(t);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
context.siblings = siblings;
|
|
}
|
|
return context.siblings;
|
|
}
|
|
|
|
function getPropertiesOfContext(context: WideningContext): Symbol[] {
|
|
if (!context.resolvedProperties) {
|
|
const names = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
|
|
for (const t of getSiblingsOfContext(context)) {
|
|
if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) {
|
|
for (const prop of getPropertiesOfType(t)) {
|
|
names.set(prop.escapedName, prop);
|
|
}
|
|
}
|
|
}
|
|
context.resolvedProperties = arrayFrom(names.values());
|
|
}
|
|
return context.resolvedProperties;
|
|
}
|
|
|
|
function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol {
|
|
if (!(prop.flags & SymbolFlags.Property)) {
|
|
// Since get accessors already widen their return value there is no need to
|
|
// widen accessor based properties here.
|
|
return prop;
|
|
}
|
|
const original = getTypeOfSymbol(prop);
|
|
const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined);
|
|
const widened = getWidenedTypeWithContext(original, propContext);
|
|
return widened === original ? prop : createSymbolWithType(prop, widened);
|
|
}
|
|
|
|
function getUndefinedProperty(prop: Symbol) {
|
|
const cached = undefinedProperties.get(prop.escapedName);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const result = createSymbolWithType(prop, undefinedType);
|
|
result.flags |= SymbolFlags.Optional;
|
|
undefinedProperties.set(prop.escapedName, result);
|
|
return result;
|
|
}
|
|
|
|
function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext | undefined): Type {
|
|
const members = createSymbolTable();
|
|
for (const prop of getPropertiesOfObjectType(type)) {
|
|
members.set(prop.escapedName, getWidenedProperty(prop, context));
|
|
}
|
|
if (context) {
|
|
for (const prop of getPropertiesOfContext(context)) {
|
|
if (!members.has(prop.escapedName)) {
|
|
members.set(prop.escapedName, getUndefinedProperty(prop));
|
|
}
|
|
}
|
|
}
|
|
const stringIndexInfo = getIndexInfoOfType(type, IndexKind.String);
|
|
const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number);
|
|
const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray,
|
|
stringIndexInfo && createIndexInfo(getWidenedType(stringIndexInfo.type), stringIndexInfo.isReadonly),
|
|
numberIndexInfo && createIndexInfo(getWidenedType(numberIndexInfo.type), numberIndexInfo.isReadonly));
|
|
result.objectFlags |= (getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType)); // Retain js literal flag through widening
|
|
return result;
|
|
}
|
|
|
|
function getWidenedType(type: Type) {
|
|
return getWidenedTypeWithContext(type, /*context*/ undefined);
|
|
}
|
|
|
|
function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type {
|
|
if (getObjectFlags(type) & ObjectFlags.RequiresWidening) {
|
|
if (context === undefined && type.widened) {
|
|
return type.widened;
|
|
}
|
|
let result: Type | undefined;
|
|
if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) {
|
|
result = anyType;
|
|
}
|
|
else if (isObjectLiteralType(type)) {
|
|
result = getWidenedTypeOfObjectLiteral(type, context);
|
|
}
|
|
else if (type.flags & TypeFlags.Union) {
|
|
const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (<UnionType>type).types);
|
|
const widenedTypes = sameMap((<UnionType>type).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext));
|
|
// Widening an empty object literal transitions from a highly restrictive type to
|
|
// a highly inclusive one. For that reason we perform subtype reduction here if the
|
|
// union includes empty object types (e.g. reducing {} | string to just {}).
|
|
result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal);
|
|
}
|
|
else if (type.flags & TypeFlags.Intersection) {
|
|
result = getIntersectionType(sameMap((<IntersectionType>type).types, getWidenedType));
|
|
}
|
|
else if (isArrayType(type) || isTupleType(type)) {
|
|
result = createTypeReference((<TypeReference>type).target, sameMap(getTypeArguments(<TypeReference>type), getWidenedType));
|
|
}
|
|
if (result && context === undefined) {
|
|
type.widened = result;
|
|
}
|
|
return result || type;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Reports implicit any errors that occur as a result of widening 'null' and 'undefined'
|
|
* to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to
|
|
* getWidenedType. But in some cases getWidenedType is called without reporting errors
|
|
* (type argument inference is an example).
|
|
*
|
|
* The return value indicates whether an error was in fact reported. The particular circumstances
|
|
* are on a best effort basis. Currently, if the null or undefined that causes widening is inside
|
|
* an object literal property (arbitrarily deeply), this function reports an error. If no error is
|
|
* reported, reportImplicitAnyError is a suitable fallback to report a general error.
|
|
*/
|
|
function reportWideningErrorsInType(type: Type): boolean {
|
|
let errorReported = false;
|
|
if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) {
|
|
if (type.flags & TypeFlags.Union) {
|
|
if (some((<UnionType>type).types, isEmptyObjectType)) {
|
|
errorReported = true;
|
|
}
|
|
else {
|
|
for (const t of (<UnionType>type).types) {
|
|
if (reportWideningErrorsInType(t)) {
|
|
errorReported = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (isArrayType(type) || isTupleType(type)) {
|
|
for (const t of getTypeArguments(<TypeReference>type)) {
|
|
if (reportWideningErrorsInType(t)) {
|
|
errorReported = true;
|
|
}
|
|
}
|
|
}
|
|
if (isObjectLiteralType(type)) {
|
|
for (const p of getPropertiesOfObjectType(type)) {
|
|
const t = getTypeOfSymbol(p);
|
|
if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) {
|
|
if (!reportWideningErrorsInType(t)) {
|
|
error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t)));
|
|
}
|
|
errorReported = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return errorReported;
|
|
}
|
|
|
|
function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) {
|
|
const typeAsString = typeToString(getWidenedType(type));
|
|
if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) {
|
|
// Only report implicit any errors/suggestions in TS and ts-check JS files
|
|
return;
|
|
}
|
|
let diagnostic: DiagnosticMessage;
|
|
switch (declaration.kind) {
|
|
case SyntaxKind.BinaryExpression:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
|
|
break;
|
|
case SyntaxKind.Parameter:
|
|
const param = declaration as ParameterDeclaration;
|
|
if (isIdentifier(param.name) &&
|
|
(isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) &&
|
|
param.parent.parameters.indexOf(param) > -1 &&
|
|
(resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) ||
|
|
param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) {
|
|
const newName = "arg" + param.parent.parameters.indexOf(param);
|
|
errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, declarationNameToString(param.name));
|
|
return;
|
|
}
|
|
diagnostic = (<ParameterDeclaration>declaration).dotDotDotToken ?
|
|
noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage :
|
|
noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
|
|
break;
|
|
case SyntaxKind.BindingElement:
|
|
diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type;
|
|
if (!noImplicitAny) {
|
|
// Don't issue a suggestion for binding elements since the codefix doesn't yet support them.
|
|
return;
|
|
}
|
|
break;
|
|
case SyntaxKind.JSDocFunctionType:
|
|
error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString);
|
|
return;
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
if (noImplicitAny && !(declaration as NamedDeclaration).name) {
|
|
if (wideningKind === WideningKind.GeneratorYield) {
|
|
error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type_annotation, typeAsString);
|
|
}
|
|
else {
|
|
error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString);
|
|
}
|
|
return;
|
|
}
|
|
diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage :
|
|
wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type :
|
|
Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type;
|
|
break;
|
|
case SyntaxKind.MappedType:
|
|
if (noImplicitAny) {
|
|
error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type);
|
|
}
|
|
return;
|
|
default:
|
|
diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
|
|
}
|
|
errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString);
|
|
}
|
|
|
|
function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) {
|
|
if (produceDiagnostics && noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType) {
|
|
// Report implicit any error within type if possible, otherwise report error on declaration
|
|
if (!reportWideningErrorsInType(type)) {
|
|
reportImplicitAny(declaration, type, wideningKind);
|
|
}
|
|
}
|
|
}
|
|
|
|
function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) {
|
|
const sourceCount = getParameterCount(source);
|
|
const targetCount = getParameterCount(target);
|
|
const sourceRestType = getEffectiveRestType(source);
|
|
const targetRestType = getEffectiveRestType(target);
|
|
const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount;
|
|
const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount);
|
|
const sourceThisType = getThisTypeOfSignature(source);
|
|
if (sourceThisType) {
|
|
const targetThisType = getThisTypeOfSignature(target);
|
|
if (targetThisType) {
|
|
callback(sourceThisType, targetThisType);
|
|
}
|
|
}
|
|
for (let i = 0; i < paramCount; i++) {
|
|
callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i));
|
|
}
|
|
if (targetRestType) {
|
|
callback(getRestTypeAtPosition(source, paramCount), targetRestType);
|
|
}
|
|
}
|
|
|
|
function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) {
|
|
const sourceTypePredicate = getTypePredicateOfSignature(source);
|
|
const targetTypePredicate = getTypePredicateOfSignature(target);
|
|
if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) {
|
|
callback(sourceTypePredicate.type, targetTypePredicate.type);
|
|
}
|
|
else {
|
|
callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
|
|
}
|
|
}
|
|
|
|
function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext {
|
|
return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable);
|
|
}
|
|
|
|
function cloneInferenceContext<T extends InferenceContext | undefined>(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined {
|
|
return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes);
|
|
}
|
|
|
|
function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext {
|
|
const context: InferenceContext = {
|
|
inferences,
|
|
signature,
|
|
flags,
|
|
compareTypes,
|
|
mapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ true)),
|
|
nonFixingMapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ false)),
|
|
};
|
|
return context;
|
|
}
|
|
|
|
function mapToInferredType(context: InferenceContext, t: Type, fix: boolean): Type {
|
|
const inferences = context.inferences;
|
|
for (let i = 0; i < inferences.length; i++) {
|
|
const inference = inferences[i];
|
|
if (t === inference.typeParameter) {
|
|
if (fix && !inference.isFixed) {
|
|
clearCachedInferences(inferences);
|
|
inference.isFixed = true;
|
|
}
|
|
return getInferredType(context, i);
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
|
|
function clearCachedInferences(inferences: InferenceInfo[]) {
|
|
for (const inference of inferences) {
|
|
if (!inference.isFixed) {
|
|
inference.inferredType = undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo {
|
|
return {
|
|
typeParameter,
|
|
candidates: undefined,
|
|
contraCandidates: undefined,
|
|
inferredType: undefined,
|
|
priority: undefined,
|
|
topLevel: true,
|
|
isFixed: false
|
|
};
|
|
}
|
|
|
|
function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo {
|
|
return {
|
|
typeParameter: inference.typeParameter,
|
|
candidates: inference.candidates && inference.candidates.slice(),
|
|
contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(),
|
|
inferredType: inference.inferredType,
|
|
priority: inference.priority,
|
|
topLevel: inference.topLevel,
|
|
isFixed: inference.isFixed
|
|
};
|
|
}
|
|
|
|
function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined {
|
|
const inferences = filter(context.inferences, hasInferenceCandidates);
|
|
return inferences.length ?
|
|
createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) :
|
|
undefined;
|
|
}
|
|
|
|
function getMapperFromContext<T extends InferenceContext | undefined>(context: T): TypeMapper | T & undefined {
|
|
return context && context.mapper;
|
|
}
|
|
|
|
// Return true if the given type could possibly reference a type parameter for which
|
|
// we perform type inference (i.e. a type parameter of a generic function). We cache
|
|
// results for union and intersection types for performance reasons.
|
|
function couldContainTypeVariables(type: Type): boolean {
|
|
const objectFlags = getObjectFlags(type);
|
|
if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) {
|
|
return !!(objectFlags & ObjectFlags.CouldContainTypeVariables);
|
|
}
|
|
const result = !!(type.flags & TypeFlags.Instantiable ||
|
|
objectFlags & ObjectFlags.Reference && ((<TypeReference>type).node || forEach(getTypeArguments(<TypeReference>type), couldContainTypeVariables)) ||
|
|
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ||
|
|
objectFlags & (ObjectFlags.Mapped | ObjectFlags.ObjectRestType) ||
|
|
type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && some((<UnionOrIntersectionType>type).types, couldContainTypeVariables));
|
|
if (type.flags & TypeFlags.ObjectFlagsType) {
|
|
(<ObjectFlagsType>type).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function isTypeParameterAtTopLevel(type: Type, typeParameter: TypeParameter): boolean {
|
|
return !!(type === typeParameter ||
|
|
type.flags & TypeFlags.UnionOrIntersection && some((<UnionOrIntersectionType>type).types, t => isTypeParameterAtTopLevel(t, typeParameter)) ||
|
|
type.flags & TypeFlags.Conditional && (
|
|
isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(<ConditionalType>type), typeParameter) ||
|
|
isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(<ConditionalType>type), typeParameter)));
|
|
}
|
|
|
|
/** Create an object with properties named in the string literal type. Every property has type `any` */
|
|
function createEmptyObjectTypeFromStringLiteral(type: Type) {
|
|
const members = createSymbolTable();
|
|
forEachType(type, t => {
|
|
if (!(t.flags & TypeFlags.StringLiteral)) {
|
|
return;
|
|
}
|
|
const name = escapeLeadingUnderscores((t as StringLiteralType).value);
|
|
const literalProp = createSymbol(SymbolFlags.Property, name);
|
|
literalProp.type = anyType;
|
|
if (t.symbol) {
|
|
literalProp.declarations = t.symbol.declarations;
|
|
literalProp.valueDeclaration = t.symbol.valueDeclaration;
|
|
}
|
|
members.set(name, literalProp);
|
|
});
|
|
const indexInfo = type.flags & TypeFlags.String ? createIndexInfo(emptyObjectType, /*isReadonly*/ false) : undefined;
|
|
return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined);
|
|
}
|
|
|
|
/**
|
|
* Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct
|
|
* an object type with the same set of properties as the source type, where the type of each
|
|
* property is computed by inferring from the source property type to X for the type
|
|
* variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for).
|
|
*/
|
|
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined {
|
|
const key = source.id + "," + target.id + "," + constraint.id;
|
|
if (reverseMappedCache.has(key)) {
|
|
return reverseMappedCache.get(key);
|
|
}
|
|
reverseMappedCache.set(key, undefined);
|
|
const type = createReverseMappedType(source, target, constraint);
|
|
reverseMappedCache.set(key, type);
|
|
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) {
|
|
// We consider a source type reverse mappable if it has a string index signature or if
|
|
// it has one or more properties and is of a partially inferable type.
|
|
if (!(getIndexInfoOfType(source, IndexKind.String) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) {
|
|
return undefined;
|
|
}
|
|
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
|
|
// applied to the element type(s).
|
|
if (isArrayType(source)) {
|
|
return createArrayType(inferReverseMappedType(getTypeArguments(<TypeReference>source)[0], target, constraint), isReadonlyArrayType(source));
|
|
}
|
|
if (isTupleType(source)) {
|
|
const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint));
|
|
const minLength = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
|
|
getTypeReferenceArity(source) - (source.target.hasRestElement ? 1 : 0) : source.target.minLength;
|
|
return createTupleType(elementTypes, minLength, source.target.hasRestElement, source.target.readonly, source.target.associatedNames);
|
|
}
|
|
// For all other object types we infer a new object type where the reverse mapping has been
|
|
// applied to the type of each property.
|
|
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
|
|
reversed.source = source;
|
|
reversed.mappedType = target;
|
|
reversed.constraintType = constraint;
|
|
return reversed;
|
|
}
|
|
|
|
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) {
|
|
return inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType);
|
|
}
|
|
|
|
function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type {
|
|
const typeParameter = <TypeParameter>getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target));
|
|
const templateType = getTemplateTypeFromMappedType(target);
|
|
const inference = createInferenceInfo(typeParameter);
|
|
inferTypes([inference], sourceType, templateType);
|
|
return getTypeFromInference(inference) || unknownType;
|
|
}
|
|
|
|
function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator<Symbol> {
|
|
const properties = getPropertiesOfType(target);
|
|
for (const targetProp of properties) {
|
|
// TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass
|
|
if (isStaticPrivateIdentifierProperty(targetProp)) {
|
|
continue;
|
|
}
|
|
if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) {
|
|
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
|
|
if (!sourceProp) {
|
|
yield targetProp;
|
|
}
|
|
else if (matchDiscriminantProperties) {
|
|
const targetType = getTypeOfSymbol(targetProp);
|
|
if (targetType.flags & TypeFlags.Unit) {
|
|
const sourceType = getTypeOfSymbol(sourceProp);
|
|
if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) {
|
|
yield targetProp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined {
|
|
const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next();
|
|
if (!result.done) return result.value;
|
|
}
|
|
|
|
function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) {
|
|
return target.target.minLength > source.target.minLength ||
|
|
!getRestTypeOfTupleType(target) && (!!getRestTypeOfTupleType(source) || getLengthOfTupleType(target) < getLengthOfTupleType(source));
|
|
}
|
|
|
|
function typesDefinitelyUnrelated(source: Type, target: Type) {
|
|
// Two tuple types with incompatible arities are definitely unrelated.
|
|
// Two object types that each have a property that is unmatched in the other are definitely unrelated.
|
|
return isTupleType(source) && isTupleType(target) && tupleTypesDefinitelyUnrelated(source, target) ||
|
|
!!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) &&
|
|
!!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true);
|
|
}
|
|
|
|
function getTypeFromInference(inference: InferenceInfo) {
|
|
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) :
|
|
inference.contraCandidates ? getIntersectionType(inference.contraCandidates) :
|
|
undefined;
|
|
}
|
|
|
|
function hasSkipDirectInferenceFlag(node: Node) {
|
|
return !!getNodeLinks(node).skipDirectInference;
|
|
}
|
|
|
|
function isFromInferenceBlockedSource(type: Type) {
|
|
return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag));
|
|
}
|
|
|
|
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) {
|
|
let symbolStack: Symbol[];
|
|
let visited: Map<number>;
|
|
let bivariant = false;
|
|
let propagationType: Type;
|
|
let inferencePriority = InferencePriority.MaxValue;
|
|
let allowComplexConstraintInference = true;
|
|
inferFromTypes(originalSource, originalTarget);
|
|
|
|
function inferFromTypes(source: Type, target: Type): void {
|
|
if (!couldContainTypeVariables(target)) {
|
|
return;
|
|
}
|
|
if (source === wildcardType) {
|
|
// We are inferring from an 'any' type. We want to infer this type for every type parameter
|
|
// referenced in the target type, so we record it as the propagation type and infer from the
|
|
// target to itself. Then, as we find candidates we substitute the propagation type.
|
|
const savePropagationType = propagationType;
|
|
propagationType = source;
|
|
inferFromTypes(target, target);
|
|
propagationType = savePropagationType;
|
|
return;
|
|
}
|
|
if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
|
|
// Source and target are types originating in the same generic type alias declaration.
|
|
// Simply infer from source type arguments to target type arguments.
|
|
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol));
|
|
return;
|
|
}
|
|
if (source === target && source.flags & TypeFlags.UnionOrIntersection) {
|
|
// When source and target are the same union or intersection type, just relate each constituent
|
|
// type to itself.
|
|
for (const t of (<UnionOrIntersectionType>source).types) {
|
|
inferFromTypes(t, t);
|
|
}
|
|
return;
|
|
}
|
|
if (target.flags & TypeFlags.Union) {
|
|
// First, infer between identically matching source and target constituents and remove the
|
|
// matching types.
|
|
const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (<UnionType>source).types : [source], (<UnionType>target).types, isTypeOrBaseIdenticalTo);
|
|
// Next, infer between closely matching source and target constituents and remove
|
|
// the matching types. Types closely match when they are instantiations of the same
|
|
// object type or instantiations of the same type alias.
|
|
const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy);
|
|
if (targets.length === 0) {
|
|
return;
|
|
}
|
|
target = getUnionType(targets);
|
|
if (sources.length === 0) {
|
|
// All source constituents have been matched and there is nothing further to infer from.
|
|
// However, simply making no inferences is undesirable because it could ultimately mean
|
|
// inferring a type parameter constraint. Instead, make a lower priority inference from
|
|
// the full source to whatever remains in the target. For example, when inferring from
|
|
// string to 'string | T', make a lower priority inference of string for T.
|
|
inferWithPriority(source, target, InferencePriority.NakedTypeVariable);
|
|
return;
|
|
}
|
|
source = getUnionType(sources);
|
|
}
|
|
else if (target.flags & TypeFlags.Intersection && some((<IntersectionType>target).types,
|
|
t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) {
|
|
// We reduce intersection types only when they contain naked type parameters. For example, when
|
|
// inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and
|
|
// infer { extra: any } for T. But when inferring to 'string[] & Iterable<T>' we want to keep the
|
|
// string[] on the source side and infer string for T.
|
|
// Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable"
|
|
// in such scenarios.
|
|
if (!(source.flags & TypeFlags.Union)) {
|
|
// Infer between identically matching source and target constituents and remove the matching types.
|
|
const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (<IntersectionType>source).types : [source], (<IntersectionType>target).types, isTypeIdenticalTo);
|
|
if (sources.length === 0 || targets.length === 0) {
|
|
return;
|
|
}
|
|
source = getIntersectionType(sources);
|
|
target = getIntersectionType(targets);
|
|
}
|
|
}
|
|
else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
|
|
target = getActualTypeVariable(target);
|
|
}
|
|
if (target.flags & TypeFlags.TypeVariable) {
|
|
// If target is a type parameter, make an inference, unless the source type contains
|
|
// the anyFunctionType (the wildcard type that's used to avoid contextually typing functions).
|
|
// Because the anyFunctionType is internal, it should not be exposed to the user by adding
|
|
// it as an inference candidate. Hopefully, a better candidate will come along that does
|
|
// not contain anyFunctionType when we come back to this argument for its second round
|
|
// of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard
|
|
// when constructing types from type parameters that had no inference candidates).
|
|
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType ||
|
|
(priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) {
|
|
return;
|
|
}
|
|
const inference = getInferenceInfoForType(target);
|
|
if (inference) {
|
|
if (!inference.isFixed) {
|
|
if (inference.priority === undefined || priority < inference.priority) {
|
|
inference.candidates = undefined;
|
|
inference.contraCandidates = undefined;
|
|
inference.topLevel = true;
|
|
inference.priority = priority;
|
|
}
|
|
if (priority === inference.priority) {
|
|
const candidate = propagationType || source;
|
|
// We make contravariant inferences only if we are in a pure contravariant position,
|
|
// i.e. only if we have not descended into a bivariant position.
|
|
if (contravariant && !bivariant) {
|
|
if (!contains(inference.contraCandidates, candidate)) {
|
|
inference.contraCandidates = append(inference.contraCandidates, candidate);
|
|
clearCachedInferences(inferences);
|
|
}
|
|
}
|
|
else if (!contains(inference.candidates, candidate)) {
|
|
inference.candidates = append(inference.candidates, candidate);
|
|
clearCachedInferences(inferences);
|
|
}
|
|
}
|
|
if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
|
|
inference.topLevel = false;
|
|
clearCachedInferences(inferences);
|
|
}
|
|
}
|
|
inferencePriority = Math.min(inferencePriority, priority);
|
|
return;
|
|
}
|
|
else {
|
|
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
|
|
const simplified = getSimplifiedType(target, /*writing*/ false);
|
|
if (simplified !== target) {
|
|
invokeOnce(source, simplified, inferFromTypes);
|
|
}
|
|
else if (target.flags & TypeFlags.IndexedAccess) {
|
|
const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false);
|
|
// Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider
|
|
// that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can.
|
|
if (indexType.flags & TypeFlags.Instantiable) {
|
|
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false);
|
|
if (simplified && simplified !== target) {
|
|
invokeOnce(source, simplified, inferFromTypes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
|
|
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target)) &&
|
|
!((<TypeReference>source).node && (<TypeReference>target).node)) {
|
|
// If source and target are references to the same generic type, infer from type arguments
|
|
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
|
|
}
|
|
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
|
|
contravariant = !contravariant;
|
|
inferFromTypes((<IndexType>source).type, (<IndexType>target).type);
|
|
contravariant = !contravariant;
|
|
}
|
|
else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) {
|
|
const empty = createEmptyObjectTypeFromStringLiteral(source);
|
|
contravariant = !contravariant;
|
|
inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof);
|
|
contravariant = !contravariant;
|
|
}
|
|
else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
|
|
inferFromTypes((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType);
|
|
inferFromTypes((<IndexedAccessType>source).indexType, (<IndexedAccessType>target).indexType);
|
|
}
|
|
else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) {
|
|
inferFromTypes((<ConditionalType>source).checkType, (<ConditionalType>target).checkType);
|
|
inferFromTypes((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType);
|
|
inferFromTypes(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target));
|
|
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
|
|
}
|
|
else if (target.flags & TypeFlags.Conditional) {
|
|
const savePriority = priority;
|
|
priority |= contravariant ? InferencePriority.ContravariantConditional : 0;
|
|
const targetTypes = [getTrueTypeFromConditionalType(<ConditionalType>target), getFalseTypeFromConditionalType(<ConditionalType>target)];
|
|
inferToMultipleTypes(source, targetTypes, target.flags);
|
|
priority = savePriority;
|
|
}
|
|
else if (target.flags & TypeFlags.UnionOrIntersection) {
|
|
inferToMultipleTypes(source, (<UnionOrIntersectionType>target).types, target.flags);
|
|
}
|
|
else if (source.flags & TypeFlags.Union) {
|
|
// Source is a union or intersection type, infer from each constituent type
|
|
const sourceTypes = (<UnionOrIntersectionType>source).types;
|
|
for (const sourceType of sourceTypes) {
|
|
inferFromTypes(sourceType, target);
|
|
}
|
|
}
|
|
else {
|
|
source = getReducedType(source);
|
|
if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) {
|
|
const apparentSource = getApparentType(source);
|
|
// getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type.
|
|
// If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes`
|
|
// with the simplified source.
|
|
if (apparentSource !== source && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) {
|
|
// TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints!
|
|
// This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference
|
|
// that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves
|
|
// here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations
|
|
// (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit.
|
|
// TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just
|
|
// remove this `allowComplexConstraintInference` flag.
|
|
allowComplexConstraintInference = false;
|
|
return inferFromTypes(apparentSource, target);
|
|
}
|
|
source = apparentSource;
|
|
}
|
|
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
|
|
invokeOnce(source, target, inferFromObjectTypes);
|
|
}
|
|
}
|
|
if (source.flags & TypeFlags.Simplifiable) {
|
|
const simplified = getSimplifiedType(source, contravariant);
|
|
if (simplified !== source) {
|
|
inferFromTypes(simplified, target);
|
|
}
|
|
}
|
|
}
|
|
|
|
function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) {
|
|
const savePriority = priority;
|
|
priority |= newPriority;
|
|
inferFromTypes(source, target);
|
|
priority = savePriority;
|
|
}
|
|
|
|
function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) {
|
|
const key = source.id + "," + target.id;
|
|
const status = visited && visited.get(key);
|
|
if (status !== undefined) {
|
|
inferencePriority = Math.min(inferencePriority, status);
|
|
return;
|
|
}
|
|
(visited || (visited = createMap<number>())).set(key, InferencePriority.Circularity);
|
|
const saveInferencePriority = inferencePriority;
|
|
inferencePriority = InferencePriority.MaxValue;
|
|
action(source, target);
|
|
visited.set(key, inferencePriority);
|
|
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
|
|
}
|
|
|
|
function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] {
|
|
let matchedSources: Type[] | undefined;
|
|
let matchedTargets: Type[] | undefined;
|
|
for (const t of targets) {
|
|
for (const s of sources) {
|
|
if (matches(s, t)) {
|
|
inferFromTypes(s, t);
|
|
matchedSources = appendIfUnique(matchedSources, s);
|
|
matchedTargets = appendIfUnique(matchedTargets, t);
|
|
}
|
|
}
|
|
}
|
|
return [
|
|
matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources,
|
|
matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets,
|
|
];
|
|
}
|
|
|
|
function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) {
|
|
const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
|
|
for (let i = 0; i < count; i++) {
|
|
if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) {
|
|
inferFromContravariantTypes(sourceTypes[i], targetTypes[i]);
|
|
}
|
|
else {
|
|
inferFromTypes(sourceTypes[i], targetTypes[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function inferFromContravariantTypes(source: Type, target: Type) {
|
|
if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) {
|
|
contravariant = !contravariant;
|
|
inferFromTypes(source, target);
|
|
contravariant = !contravariant;
|
|
}
|
|
else {
|
|
inferFromTypes(source, target);
|
|
}
|
|
}
|
|
|
|
function getInferenceInfoForType(type: Type) {
|
|
if (type.flags & TypeFlags.TypeVariable) {
|
|
for (const inference of inferences) {
|
|
if (type === inference.typeParameter) {
|
|
return inference;
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getSingleTypeVariableFromIntersectionTypes(types: Type[]) {
|
|
let typeVariable: Type | undefined;
|
|
for (const type of types) {
|
|
const t = type.flags & TypeFlags.Intersection && find((<IntersectionType>type).types, t => !!getInferenceInfoForType(t));
|
|
if (!t || typeVariable && t !== typeVariable) {
|
|
return undefined;
|
|
}
|
|
typeVariable = t;
|
|
}
|
|
return typeVariable;
|
|
}
|
|
|
|
function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) {
|
|
let typeVariableCount = 0;
|
|
if (targetFlags & TypeFlags.Union) {
|
|
let nakedTypeVariable: Type | undefined;
|
|
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
|
|
const matched = new Array<boolean>(sources.length);
|
|
let inferenceCircularity = false;
|
|
// First infer to types that are not naked type variables. For each source type we
|
|
// track whether inferences were made from that particular type to some target with
|
|
// equal priority (i.e. of equal quality) to what we would infer for a naked type
|
|
// parameter.
|
|
for (const t of targets) {
|
|
if (getInferenceInfoForType(t)) {
|
|
nakedTypeVariable = t;
|
|
typeVariableCount++;
|
|
}
|
|
else {
|
|
for (let i = 0; i < sources.length; i++) {
|
|
const saveInferencePriority = inferencePriority;
|
|
inferencePriority = InferencePriority.MaxValue;
|
|
inferFromTypes(sources[i], t);
|
|
if (inferencePriority === priority) matched[i] = true;
|
|
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
|
|
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
|
|
}
|
|
}
|
|
}
|
|
if (typeVariableCount === 0) {
|
|
// If every target is an intersection of types containing a single naked type variable,
|
|
// make a lower priority inference to that type variable. This handles inferring from
|
|
// 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T.
|
|
const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets);
|
|
if (intersectionTypeVariable) {
|
|
inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable);
|
|
}
|
|
return;
|
|
}
|
|
// If the target has a single naked type variable and no inference circularities were
|
|
// encountered above (meaning we explored the types fully), create a union of the source
|
|
// types from which no inferences have been made so far and infer from that union to the
|
|
// naked type variable.
|
|
if (typeVariableCount === 1 && !inferenceCircularity) {
|
|
const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s);
|
|
if (unmatched.length) {
|
|
inferFromTypes(getUnionType(unmatched), nakedTypeVariable!);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// We infer from types that are not naked type variables first so that inferences we
|
|
// make from nested naked type variables and given slightly higher priority by virtue
|
|
// of being first in the candidates array.
|
|
for (const t of targets) {
|
|
if (getInferenceInfoForType(t)) {
|
|
typeVariableCount++;
|
|
}
|
|
else {
|
|
inferFromTypes(source, t);
|
|
}
|
|
}
|
|
}
|
|
// Inferences directly to naked type variables are given lower priority as they are
|
|
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
|
|
// we want to infer string for T, not Promise<string> | string. For intersection types
|
|
// we only infer to single naked type variables.
|
|
if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) {
|
|
for (const t of targets) {
|
|
if (getInferenceInfoForType(t)) {
|
|
inferWithPriority(source, t, InferencePriority.NakedTypeVariable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean {
|
|
if (constraintType.flags & TypeFlags.Union) {
|
|
let result = false;
|
|
for (const type of (constraintType as UnionType).types) {
|
|
result = inferToMappedType(source, target, type) || result;
|
|
}
|
|
return result;
|
|
}
|
|
if (constraintType.flags & TypeFlags.Index) {
|
|
// We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X },
|
|
// where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source
|
|
// type and then make a secondary inference from that type to T. We make a secondary inference
|
|
// such that direct inferences to T get priority over inferences to Partial<T>, for example.
|
|
const inference = getInferenceInfoForType((<IndexType>constraintType).type);
|
|
if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) {
|
|
const inferredType = inferTypeForHomomorphicMappedType(source, target, <IndexType>constraintType);
|
|
if (inferredType) {
|
|
// We assign a lower priority to inferences made from types containing non-inferrable
|
|
// types because we may only have a partial result (i.e. we may have failed to make
|
|
// reverse inferences for some properties).
|
|
inferWithPriority(inferredType, inference.typeParameter,
|
|
getObjectFlags(source) & ObjectFlags.NonInferrableType ?
|
|
InferencePriority.PartialHomomorphicMappedType :
|
|
InferencePriority.HomomorphicMappedType);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (constraintType.flags & TypeFlags.TypeParameter) {
|
|
// We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type
|
|
// parameter. First infer from 'keyof S' to K.
|
|
inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint);
|
|
// If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X },
|
|
// where K extends keyof T, we make the same inferences as for a homomorphic mapped type
|
|
// { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a
|
|
// Pick<T, K>.
|
|
const extendedConstraint = getConstraintOfType(constraintType);
|
|
if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) {
|
|
return true;
|
|
}
|
|
// If no inferences can be made to K's constraint, infer from a union of the property types
|
|
// in the source to the template type X.
|
|
const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol);
|
|
const stringIndexType = getIndexTypeOfType(source, IndexKind.String);
|
|
const numberIndexInfo = getNonEnumNumberIndexInfo(source);
|
|
const numberIndexType = numberIndexInfo && numberIndexInfo.type;
|
|
inferFromTypes(getUnionType(append(append(propTypes, stringIndexType), numberIndexType)), getTemplateTypeFromMappedType(target));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function inferFromObjectTypes(source: Type, target: Type) {
|
|
// If we are already processing another target type with the same associated symbol (such as
|
|
// an instantiation of the same generic type), we do not explore this target as it would yield
|
|
// no further inferences. We exclude the static side of classes from this check since it shares
|
|
// its symbol with the instance side which would lead to false positives.
|
|
const isNonConstructorObject = target.flags & TypeFlags.Object &&
|
|
!(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class);
|
|
const symbol = isNonConstructorObject ? target.symbol : undefined;
|
|
if (symbol) {
|
|
if (contains(symbolStack, symbol)) {
|
|
inferencePriority = InferencePriority.Circularity;
|
|
return;
|
|
}
|
|
(symbolStack || (symbolStack = [])).push(symbol);
|
|
inferFromObjectTypesWorker(source, target);
|
|
symbolStack.pop();
|
|
}
|
|
else {
|
|
inferFromObjectTypesWorker(source, target);
|
|
}
|
|
}
|
|
|
|
function inferFromObjectTypesWorker(source: Type, target: Type) {
|
|
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
|
|
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
|
|
// If source and target are references to the same generic type, infer from type arguments
|
|
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
|
|
return;
|
|
}
|
|
if (isGenericMappedType(source) && isGenericMappedType(target)) {
|
|
// The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
|
|
// from S to T and from X to Y.
|
|
inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target));
|
|
inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target));
|
|
}
|
|
if (getObjectFlags(target) & ObjectFlags.Mapped) {
|
|
const constraintType = getConstraintTypeFromMappedType(<MappedType>target);
|
|
if (inferToMappedType(source, <MappedType>target, constraintType)) {
|
|
return;
|
|
}
|
|
}
|
|
// Infer from the members of source and target only if the two types are possibly related
|
|
if (!typesDefinitelyUnrelated(source, target)) {
|
|
if (isArrayType(source) || isTupleType(source)) {
|
|
if (isTupleType(target)) {
|
|
const sourceLength = isTupleType(source) ? getLengthOfTupleType(source) : 0;
|
|
const targetLength = getLengthOfTupleType(target);
|
|
const sourceRestType = isTupleType(source) ? getRestTypeOfTupleType(source) : getElementTypeOfArrayType(source);
|
|
const targetRestType = getRestTypeOfTupleType(target);
|
|
const fixedLength = targetLength < sourceLength || sourceRestType ? targetLength : sourceLength;
|
|
for (let i = 0; i < fixedLength; i++) {
|
|
inferFromTypes(i < sourceLength ? getTypeArguments(<TypeReference>source)[i] : sourceRestType!, getTypeArguments(target)[i]);
|
|
}
|
|
if (targetRestType) {
|
|
const types = fixedLength < sourceLength ? getTypeArguments(<TypeReference>source).slice(fixedLength, sourceLength) : [];
|
|
if (sourceRestType) {
|
|
types.push(sourceRestType);
|
|
}
|
|
if (types.length) {
|
|
inferFromTypes(getUnionType(types), targetRestType);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (isArrayType(target)) {
|
|
inferFromIndexTypes(source, target);
|
|
return;
|
|
}
|
|
}
|
|
inferFromProperties(source, target);
|
|
inferFromSignatures(source, target, SignatureKind.Call);
|
|
inferFromSignatures(source, target, SignatureKind.Construct);
|
|
inferFromIndexTypes(source, target);
|
|
}
|
|
}
|
|
|
|
function inferFromProperties(source: Type, target: Type) {
|
|
const properties = getPropertiesOfObjectType(target);
|
|
for (const targetProp of properties) {
|
|
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
|
|
if (sourceProp) {
|
|
inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp));
|
|
}
|
|
}
|
|
}
|
|
|
|
function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) {
|
|
const sourceSignatures = getSignaturesOfType(source, kind);
|
|
const targetSignatures = getSignaturesOfType(target, kind);
|
|
const sourceLen = sourceSignatures.length;
|
|
const targetLen = targetSignatures.length;
|
|
const len = sourceLen < targetLen ? sourceLen : targetLen;
|
|
const skipParameters = !!(getObjectFlags(source) & ObjectFlags.NonInferrableType);
|
|
for (let i = 0; i < len; i++) {
|
|
inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i]), skipParameters);
|
|
}
|
|
}
|
|
|
|
function inferFromSignature(source: Signature, target: Signature, skipParameters: boolean) {
|
|
if (!skipParameters) {
|
|
const saveBivariant = bivariant;
|
|
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
|
|
// Once we descend into a bivariant signature we remain bivariant for all nested inferences
|
|
bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor;
|
|
applyToParameterTypes(source, target, inferFromContravariantTypes);
|
|
bivariant = saveBivariant;
|
|
}
|
|
applyToReturnTypes(source, target, inferFromTypes);
|
|
}
|
|
|
|
function inferFromIndexTypes(source: Type, target: Type) {
|
|
const targetStringIndexType = getIndexTypeOfType(target, IndexKind.String);
|
|
if (targetStringIndexType) {
|
|
const sourceIndexType = getIndexTypeOfType(source, IndexKind.String) ||
|
|
getImplicitIndexTypeOfType(source, IndexKind.String);
|
|
if (sourceIndexType) {
|
|
inferFromTypes(sourceIndexType, targetStringIndexType);
|
|
}
|
|
}
|
|
const targetNumberIndexType = getIndexTypeOfType(target, IndexKind.Number);
|
|
if (targetNumberIndexType) {
|
|
const sourceIndexType = getIndexTypeOfType(source, IndexKind.Number) ||
|
|
getIndexTypeOfType(source, IndexKind.String) ||
|
|
getImplicitIndexTypeOfType(source, IndexKind.Number);
|
|
if (sourceIndexType) {
|
|
inferFromTypes(sourceIndexType, targetNumberIndexType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isTypeOrBaseIdenticalTo(s: Type, t: Type) {
|
|
return isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral);
|
|
}
|
|
|
|
function isTypeCloselyMatchedBy(s: Type, t: Type) {
|
|
return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol ||
|
|
s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol);
|
|
}
|
|
|
|
function hasPrimitiveConstraint(type: TypeParameter): boolean {
|
|
const constraint = getConstraintOfTypeParameter(type);
|
|
return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index);
|
|
}
|
|
|
|
function isObjectLiteralType(type: Type) {
|
|
return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral);
|
|
}
|
|
|
|
function isObjectOrArrayLiteralType(type: Type) {
|
|
return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral));
|
|
}
|
|
|
|
function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] {
|
|
if (candidates.length > 1) {
|
|
const objectLiterals = filter(candidates, isObjectOrArrayLiteralType);
|
|
if (objectLiterals.length) {
|
|
const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype);
|
|
return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]);
|
|
}
|
|
}
|
|
return candidates;
|
|
}
|
|
|
|
function getContravariantInference(inference: InferenceInfo) {
|
|
return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!);
|
|
}
|
|
|
|
function getCovariantInference(inference: InferenceInfo, signature: Signature) {
|
|
// Extract all object and array literal types and replace them with a single widened and normalized type.
|
|
const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!);
|
|
// We widen inferred literal types if
|
|
// all inferences were made to top-level occurrences of the type parameter, and
|
|
// the type parameter has no constraint or its constraint includes no primitive or literal types, and
|
|
// the type parameter was fixed during inference or does not occur at top-level in the return type.
|
|
const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter);
|
|
const widenLiteralTypes = !primitiveConstraint && inference.topLevel &&
|
|
(inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter));
|
|
const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) :
|
|
widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) :
|
|
candidates;
|
|
// If all inferences were made from a position that implies a combined result, infer a union type.
|
|
// Otherwise, infer a common supertype.
|
|
const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ?
|
|
getUnionType(baseCandidates, UnionReduction.Subtype) :
|
|
getCommonSupertype(baseCandidates);
|
|
return getWidenedType(unwidenedType);
|
|
}
|
|
|
|
function getInferredType(context: InferenceContext, index: number): Type {
|
|
const inference = context.inferences[index];
|
|
if (!inference.inferredType) {
|
|
let inferredType: Type | undefined;
|
|
const signature = context.signature;
|
|
if (signature) {
|
|
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined;
|
|
if (inference.contraCandidates) {
|
|
const inferredContravariantType = getContravariantInference(inference);
|
|
// If we have both co- and contra-variant inferences, we prefer the contra-variant inference
|
|
// unless the co-variant inference is a subtype and not 'never'.
|
|
inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
|
|
isTypeSubtypeOf(inferredCovariantType, inferredContravariantType) ?
|
|
inferredCovariantType : inferredContravariantType;
|
|
}
|
|
else if (inferredCovariantType) {
|
|
inferredType = inferredCovariantType;
|
|
}
|
|
else if (context.flags & InferenceFlags.NoDefault) {
|
|
// We use silentNeverType as the wildcard that signals no inferences.
|
|
inferredType = silentNeverType;
|
|
}
|
|
else {
|
|
// Infer either the default or the empty object type when no inferences were
|
|
// made. It is important to remember that in this case, inference still
|
|
// succeeds, meaning there is no error for not having inference candidates. An
|
|
// inference error only occurs when there are *conflicting* candidates, i.e.
|
|
// candidates with no common supertype.
|
|
const defaultType = getDefaultFromTypeParameter(inference.typeParameter);
|
|
if (defaultType) {
|
|
// Instantiate the default type. Any forward reference to a type
|
|
// parameter should be instantiated to the empty object type.
|
|
inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
inferredType = getTypeFromInference(inference);
|
|
}
|
|
|
|
inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault));
|
|
|
|
const constraint = getConstraintOfTypeParameter(inference.typeParameter);
|
|
if (constraint) {
|
|
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
|
|
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
|
|
inference.inferredType = inferredType = instantiatedConstraint;
|
|
}
|
|
}
|
|
}
|
|
|
|
return inference.inferredType;
|
|
}
|
|
|
|
function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type {
|
|
return isInJavaScriptFile ? anyType : unknownType;
|
|
}
|
|
|
|
function getInferredTypes(context: InferenceContext): Type[] {
|
|
const result: Type[] = [];
|
|
for (let i = 0; i < context.inferences.length; i++) {
|
|
result.push(getInferredType(context, i));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// EXPRESSION TYPE CHECKING
|
|
|
|
function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage {
|
|
switch (node.escapedText) {
|
|
case "document":
|
|
case "console":
|
|
return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom;
|
|
case "$":
|
|
return compilerOptions.types
|
|
? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig
|
|
: Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_types_Slashjquery;
|
|
case "describe":
|
|
case "suite":
|
|
case "it":
|
|
case "test":
|
|
return compilerOptions.types
|
|
? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_types_Slashjest_or_npm_i_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig
|
|
: Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_types_Slashjest_or_npm_i_types_Slashmocha;
|
|
case "process":
|
|
case "require":
|
|
case "Buffer":
|
|
case "module":
|
|
return compilerOptions.types
|
|
? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig
|
|
: Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_types_Slashnode;
|
|
case "Map":
|
|
case "Set":
|
|
case "Promise":
|
|
case "Symbol":
|
|
case "WeakMap":
|
|
case "WeakSet":
|
|
case "Iterator":
|
|
case "AsyncIterator":
|
|
return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later;
|
|
default:
|
|
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer;
|
|
}
|
|
else {
|
|
return Diagnostics.Cannot_find_name_0;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getResolvedSymbol(node: Identifier): Symbol {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedSymbol) {
|
|
links.resolvedSymbol = !nodeIsMissing(node) &&
|
|
resolveName(
|
|
node,
|
|
node.escapedText,
|
|
SymbolFlags.Value | SymbolFlags.ExportValue,
|
|
getCannotFindNameDiagnosticForName(node),
|
|
node,
|
|
!isWriteOnlyAccess(node),
|
|
/*excludeGlobals*/ false,
|
|
Diagnostics.Cannot_find_name_0_Did_you_mean_1) || unknownSymbol;
|
|
}
|
|
return links.resolvedSymbol;
|
|
}
|
|
|
|
function isInTypeQuery(node: Node): boolean {
|
|
// TypeScript 1.0 spec (April 2014): 3.6.3
|
|
// A type query consists of the keyword typeof followed by an expression.
|
|
// The expression is restricted to a single identifier or a sequence of identifiers separated by periods
|
|
return !!findAncestor(
|
|
node,
|
|
n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit");
|
|
}
|
|
|
|
// Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers
|
|
// separated by dots). The key consists of the id of the symbol referenced by the
|
|
// leftmost identifier followed by zero or more property names separated by dots.
|
|
// The result is undefined if the reference isn't a dotted name. We prefix nodes
|
|
// occurring in an apparent type position with '@' because the control flow type
|
|
// of such nodes may be based on the apparent type instead of the declared type.
|
|
function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
const symbol = getResolvedSymbol(<Identifier>node);
|
|
return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined;
|
|
case SyntaxKind.ThisKeyword:
|
|
return "0";
|
|
case SyntaxKind.NonNullExpression:
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return getFlowCacheKey((<NonNullExpression | ParenthesizedExpression>node).expression, declaredType, initialType, flowContainer);
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
const propName = getAccessedPropertyName(<AccessExpression>node);
|
|
if (propName !== undefined) {
|
|
const key = getFlowCacheKey((<AccessExpression>node).expression, declaredType, initialType, flowContainer);
|
|
return key && key + "." + propName;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function isMatchingReference(source: Node, target: Node): boolean {
|
|
switch (target.kind) {
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
case SyntaxKind.NonNullExpression:
|
|
return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression);
|
|
}
|
|
switch (source.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return target.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>source) === getResolvedSymbol(<Identifier>target) ||
|
|
(target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) &&
|
|
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>source)) === getSymbolOfNode(target);
|
|
case SyntaxKind.ThisKeyword:
|
|
return target.kind === SyntaxKind.ThisKeyword;
|
|
case SyntaxKind.SuperKeyword:
|
|
return target.kind === SyntaxKind.SuperKeyword;
|
|
case SyntaxKind.NonNullExpression:
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target);
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
return isAccessExpression(target) &&
|
|
getAccessedPropertyName(<AccessExpression>source) === getAccessedPropertyName(target) &&
|
|
isMatchingReference((<AccessExpression>source).expression, target.expression);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getAccessedPropertyName(access: AccessExpression): __String | undefined {
|
|
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
|
|
isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
|
|
undefined;
|
|
}
|
|
|
|
function containsMatchingReference(source: Node, target: Node) {
|
|
while (isAccessExpression(source)) {
|
|
source = source.expression;
|
|
if (isMatchingReference(source, target)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function optionalChainContainsReference(source: Node, target: Node) {
|
|
while (isOptionalChain(source)) {
|
|
source = source.expression;
|
|
if (isMatchingReference(source, target)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isDiscriminantProperty(type: Type | undefined, name: __String) {
|
|
if (type && type.flags & TypeFlags.Union) {
|
|
const prop = getUnionOrIntersectionProperty(<UnionType>type, name);
|
|
if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) {
|
|
if ((<TransientSymbol>prop).isDiscriminantProperty === undefined) {
|
|
(<TransientSymbol>prop).isDiscriminantProperty =
|
|
((<TransientSymbol>prop).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant &&
|
|
!maybeTypeOfKind(getTypeOfSymbol(prop), TypeFlags.Instantiable);
|
|
}
|
|
return !!(<TransientSymbol>prop).isDiscriminantProperty;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined {
|
|
let result: Symbol[] | undefined;
|
|
for (const sourceProperty of sourceProperties) {
|
|
if (isDiscriminantProperty(target, sourceProperty.escapedName)) {
|
|
if (result) {
|
|
result.push(sourceProperty);
|
|
continue;
|
|
}
|
|
result = [sourceProperty];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function isOrContainsMatchingReference(source: Node, target: Node) {
|
|
return isMatchingReference(source, target) || containsMatchingReference(source, target);
|
|
}
|
|
|
|
function hasMatchingArgument(callExpression: CallExpression, reference: Node) {
|
|
if (callExpression.arguments) {
|
|
for (const argument of callExpression.arguments) {
|
|
if (isOrContainsMatchingReference(reference, argument)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (callExpression.expression.kind === SyntaxKind.PropertyAccessExpression &&
|
|
isOrContainsMatchingReference(reference, (<PropertyAccessExpression>callExpression.expression).expression)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getFlowNodeId(flow: FlowNode): number {
|
|
if (!flow.id || flow.id < 0) {
|
|
flow.id = nextFlowId;
|
|
nextFlowId++;
|
|
}
|
|
return flow.id;
|
|
}
|
|
|
|
function typeMaybeAssignableTo(source: Type, target: Type) {
|
|
if (!(source.flags & TypeFlags.Union)) {
|
|
return isTypeAssignableTo(source, target);
|
|
}
|
|
for (const t of (<UnionType>source).types) {
|
|
if (isTypeAssignableTo(t, target)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Remove those constituent types of declaredType to which no constituent type of assignedType is assignable.
|
|
// For example, when a variable of type number | string | boolean is assigned a value of type number | boolean,
|
|
// we remove type string.
|
|
function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) {
|
|
if (declaredType !== assignedType) {
|
|
if (assignedType.flags & TypeFlags.Never) {
|
|
return assignedType;
|
|
}
|
|
let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
|
|
if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) {
|
|
reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types
|
|
}
|
|
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
|
|
// For now, when that happens, we give up and don't narrow at all. (This also
|
|
// means we'll never narrow for erroneous assignments where the assigned type
|
|
// is not assignable to the declared type.)
|
|
if (isTypeAssignableTo(assignedType, reducedType)) {
|
|
return reducedType;
|
|
}
|
|
}
|
|
return declaredType;
|
|
}
|
|
|
|
function getTypeFactsOfTypes(types: Type[]): TypeFacts {
|
|
let result: TypeFacts = TypeFacts.None;
|
|
for (const t of types) {
|
|
result |= getTypeFacts(t);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function isFunctionObjectType(type: ObjectType): boolean {
|
|
// We do a quick check for a "bind" property before performing the more expensive subtype
|
|
// check. This gives us a quicker out in the common case where an object type is not a function.
|
|
const resolved = resolveStructuredTypeMembers(type);
|
|
return !!(resolved.callSignatures.length || resolved.constructSignatures.length ||
|
|
resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType));
|
|
}
|
|
|
|
function getTypeFacts(type: Type): TypeFacts {
|
|
const flags = type.flags;
|
|
if (flags & TypeFlags.String) {
|
|
return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts;
|
|
}
|
|
if (flags & TypeFlags.StringLiteral) {
|
|
const isEmpty = (<StringLiteralType>type).value === "";
|
|
return strictNullChecks ?
|
|
isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts :
|
|
isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts;
|
|
}
|
|
if (flags & (TypeFlags.Number | TypeFlags.Enum)) {
|
|
return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts;
|
|
}
|
|
if (flags & TypeFlags.NumberLiteral) {
|
|
const isZero = (<NumberLiteralType>type).value === 0;
|
|
return strictNullChecks ?
|
|
isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts :
|
|
isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts;
|
|
}
|
|
if (flags & TypeFlags.BigInt) {
|
|
return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts;
|
|
}
|
|
if (flags & TypeFlags.BigIntLiteral) {
|
|
const isZero = isZeroBigInt(<BigIntLiteralType>type);
|
|
return strictNullChecks ?
|
|
isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts :
|
|
isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts;
|
|
}
|
|
if (flags & TypeFlags.Boolean) {
|
|
return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts;
|
|
}
|
|
if (flags & TypeFlags.BooleanLike) {
|
|
return strictNullChecks ?
|
|
(type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts :
|
|
(type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
|
|
}
|
|
if (flags & TypeFlags.Object) {
|
|
return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(<ObjectType>type) ?
|
|
strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts :
|
|
isFunctionObjectType(<ObjectType>type) ?
|
|
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
|
|
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
|
|
}
|
|
if (flags & (TypeFlags.Void | TypeFlags.Undefined)) {
|
|
return TypeFacts.UndefinedFacts;
|
|
}
|
|
if (flags & TypeFlags.Null) {
|
|
return TypeFacts.NullFacts;
|
|
}
|
|
if (flags & TypeFlags.ESSymbolLike) {
|
|
return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts;
|
|
}
|
|
if (flags & TypeFlags.NonPrimitive) {
|
|
return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
|
|
}
|
|
if (flags & TypeFlags.Instantiable) {
|
|
return getTypeFacts(getBaseConstraintOfType(type) || unknownType);
|
|
}
|
|
if (flags & TypeFlags.UnionOrIntersection) {
|
|
return getTypeFactsOfTypes((<UnionOrIntersectionType>type).types);
|
|
}
|
|
return TypeFacts.All;
|
|
}
|
|
|
|
function getTypeWithFacts(type: Type, include: TypeFacts) {
|
|
return filterType(type, t => (getTypeFacts(t) & include) !== 0);
|
|
}
|
|
|
|
function getTypeWithDefault(type: Type, defaultExpression: Expression) {
|
|
if (defaultExpression) {
|
|
const defaultType = getTypeOfExpression(defaultExpression);
|
|
return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), defaultType]);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getTypeOfDestructuredProperty(type: Type, name: PropertyName) {
|
|
const nameType = getLiteralTypeFromPropertyName(name);
|
|
if (!isTypeUsableAsPropertyName(nameType)) return errorType;
|
|
const text = getPropertyNameFromType(nameType);
|
|
return getConstraintForLocation(getTypeOfPropertyOfType(type, text), name) ||
|
|
isNumericLiteralName(text) && getIndexTypeOfType(type, IndexKind.Number) ||
|
|
getIndexTypeOfType(type, IndexKind.String) ||
|
|
errorType;
|
|
}
|
|
|
|
function getTypeOfDestructuredArrayElement(type: Type, index: number) {
|
|
return everyType(type, isTupleLikeType) && getTupleElementType(type, index) ||
|
|
checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) ||
|
|
errorType;
|
|
}
|
|
|
|
function getTypeOfDestructuredSpreadExpression(type: Type) {
|
|
return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType);
|
|
}
|
|
|
|
function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type {
|
|
const isDestructuringDefaultAssignment =
|
|
node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) ||
|
|
node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent);
|
|
return isDestructuringDefaultAssignment ?
|
|
getTypeWithDefault(getAssignedType(node), node.right) :
|
|
getTypeOfExpression(node.right);
|
|
}
|
|
|
|
function isDestructuringAssignmentTarget(parent: Node) {
|
|
return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent ||
|
|
parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent;
|
|
}
|
|
|
|
function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type {
|
|
return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element));
|
|
}
|
|
|
|
function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type {
|
|
return getTypeOfDestructuredSpreadExpression(getAssignedType(<ArrayLiteralExpression>node.parent));
|
|
}
|
|
|
|
function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): Type {
|
|
return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name);
|
|
}
|
|
|
|
function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): Type {
|
|
return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!);
|
|
}
|
|
|
|
function getAssignedType(node: Expression): Type {
|
|
const { parent } = node;
|
|
switch (parent.kind) {
|
|
case SyntaxKind.ForInStatement:
|
|
return stringType;
|
|
case SyntaxKind.ForOfStatement:
|
|
return checkRightHandSideOfForOf((<ForOfStatement>parent).expression, (<ForOfStatement>parent).awaitModifier) || errorType;
|
|
case SyntaxKind.BinaryExpression:
|
|
return getAssignedTypeOfBinaryExpression(<BinaryExpression>parent);
|
|
case SyntaxKind.DeleteExpression:
|
|
return undefinedType;
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return getAssignedTypeOfArrayLiteralElement(<ArrayLiteralExpression>parent, node);
|
|
case SyntaxKind.SpreadElement:
|
|
return getAssignedTypeOfSpreadExpression(<SpreadElement>parent);
|
|
case SyntaxKind.PropertyAssignment:
|
|
return getAssignedTypeOfPropertyAssignment(<PropertyAssignment>parent);
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
return getAssignedTypeOfShorthandPropertyAssignment(<ShorthandPropertyAssignment>parent);
|
|
}
|
|
return errorType;
|
|
}
|
|
|
|
function getInitialTypeOfBindingElement(node: BindingElement): Type {
|
|
const pattern = node.parent;
|
|
const parentType = getInitialType(<VariableDeclaration | BindingElement>pattern.parent);
|
|
const type = pattern.kind === SyntaxKind.ObjectBindingPattern ?
|
|
getTypeOfDestructuredProperty(parentType, node.propertyName || <Identifier>node.name) :
|
|
!node.dotDotDotToken ?
|
|
getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) :
|
|
getTypeOfDestructuredSpreadExpression(parentType);
|
|
return getTypeWithDefault(type, node.initializer!);
|
|
}
|
|
|
|
function getTypeOfInitializer(node: Expression) {
|
|
// Return the cached type if one is available. If the type of the variable was inferred
|
|
// from its initializer, we'll already have cached the type. Otherwise we compute it now
|
|
// without caching such that transient types are reflected.
|
|
const links = getNodeLinks(node);
|
|
return links.resolvedType || getTypeOfExpression(node);
|
|
}
|
|
|
|
function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) {
|
|
if (node.initializer) {
|
|
return getTypeOfInitializer(node.initializer);
|
|
}
|
|
if (node.parent.parent.kind === SyntaxKind.ForInStatement) {
|
|
return stringType;
|
|
}
|
|
if (node.parent.parent.kind === SyntaxKind.ForOfStatement) {
|
|
return checkRightHandSideOfForOf(node.parent.parent.expression, node.parent.parent.awaitModifier) || errorType;
|
|
}
|
|
return errorType;
|
|
}
|
|
|
|
function getInitialType(node: VariableDeclaration | BindingElement) {
|
|
return node.kind === SyntaxKind.VariableDeclaration ?
|
|
getInitialTypeOfVariableDeclaration(node) :
|
|
getInitialTypeOfBindingElement(node);
|
|
}
|
|
|
|
function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) {
|
|
return node.kind === SyntaxKind.VariableDeclaration && (<VariableDeclaration>node).initializer &&
|
|
isEmptyArrayLiteral((<VariableDeclaration>node).initializer!) ||
|
|
node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression &&
|
|
isEmptyArrayLiteral((<BinaryExpression>node.parent).right);
|
|
}
|
|
|
|
function getReferenceCandidate(node: Expression): Expression {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return getReferenceCandidate((<ParenthesizedExpression>node).expression);
|
|
case SyntaxKind.BinaryExpression:
|
|
switch ((<BinaryExpression>node).operatorToken.kind) {
|
|
case SyntaxKind.EqualsToken:
|
|
return getReferenceCandidate((<BinaryExpression>node).left);
|
|
case SyntaxKind.CommaToken:
|
|
return getReferenceCandidate((<BinaryExpression>node).right);
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function getReferenceRoot(node: Node): Node {
|
|
const { parent } = node;
|
|
return parent.kind === SyntaxKind.ParenthesizedExpression ||
|
|
parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>parent).operatorToken.kind === SyntaxKind.EqualsToken && (<BinaryExpression>parent).left === node ||
|
|
parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>parent).operatorToken.kind === SyntaxKind.CommaToken && (<BinaryExpression>parent).right === node ?
|
|
getReferenceRoot(parent) : node;
|
|
}
|
|
|
|
function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) {
|
|
if (clause.kind === SyntaxKind.CaseClause) {
|
|
return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression));
|
|
}
|
|
return neverType;
|
|
}
|
|
|
|
function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] {
|
|
const links = getNodeLinks(switchStatement);
|
|
if (!links.switchTypes) {
|
|
links.switchTypes = [];
|
|
for (const clause of switchStatement.caseBlock.clauses) {
|
|
links.switchTypes.push(getTypeOfSwitchClause(clause));
|
|
}
|
|
}
|
|
return links.switchTypes;
|
|
}
|
|
|
|
// Get the types from all cases in a switch on `typeof`. An
|
|
// `undefined` element denotes an explicit `default` clause.
|
|
function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: false): string[];
|
|
function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[];
|
|
function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[] {
|
|
const witnesses: (string | undefined)[] = [];
|
|
for (const clause of switchStatement.caseBlock.clauses) {
|
|
if (clause.kind === SyntaxKind.CaseClause) {
|
|
if (isStringLiteralLike(clause.expression)) {
|
|
witnesses.push(clause.expression.text);
|
|
continue;
|
|
}
|
|
return emptyArray;
|
|
}
|
|
if (retainDefault) witnesses.push(/*explicitDefaultStatement*/ undefined);
|
|
}
|
|
return witnesses;
|
|
}
|
|
|
|
function eachTypeContainedIn(source: Type, types: Type[]) {
|
|
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
|
|
}
|
|
|
|
function isTypeSubsetOf(source: Type, target: Type) {
|
|
return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, <UnionType>target);
|
|
}
|
|
|
|
function isTypeSubsetOfUnion(source: Type, target: UnionType) {
|
|
if (source.flags & TypeFlags.Union) {
|
|
for (const t of (<UnionType>source).types) {
|
|
if (!containsType(target.types, t)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType(<LiteralType>source) === target) {
|
|
return true;
|
|
}
|
|
return containsType(target.types, source);
|
|
}
|
|
|
|
function forEachType<T>(type: Type, f: (t: Type) => T | undefined): T | undefined {
|
|
return type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, f) : f(type);
|
|
}
|
|
|
|
function everyType(type: Type, f: (t: Type) => boolean): boolean {
|
|
return type.flags & TypeFlags.Union ? every((<UnionType>type).types, f) : f(type);
|
|
}
|
|
|
|
function filterType(type: Type, f: (t: Type) => boolean): Type {
|
|
if (type.flags & TypeFlags.Union) {
|
|
const types = (<UnionType>type).types;
|
|
const filtered = filter(types, f);
|
|
return filtered === types ? type : getUnionTypeFromSortedList(filtered, (<UnionType>type).objectFlags);
|
|
}
|
|
return f(type) ? type : neverType;
|
|
}
|
|
|
|
function countTypes(type: Type) {
|
|
return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1;
|
|
}
|
|
|
|
// Apply a mapping function to a type and return the resulting type. If the source type
|
|
// is a union type, the mapping function is applied to each constituent type and a union
|
|
// of the resulting types is returned.
|
|
function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type;
|
|
function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined;
|
|
function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined {
|
|
if (type.flags & TypeFlags.Never) {
|
|
return type;
|
|
}
|
|
if (!(type.flags & TypeFlags.Union)) {
|
|
return mapper(type);
|
|
}
|
|
let mappedTypes: Type[] | undefined;
|
|
for (const t of (<UnionType>type).types) {
|
|
const mapped = mapper(t);
|
|
if (mapped) {
|
|
if (!mappedTypes) {
|
|
mappedTypes = [mapped];
|
|
}
|
|
else {
|
|
mappedTypes.push(mapped);
|
|
}
|
|
}
|
|
}
|
|
return mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal);
|
|
}
|
|
|
|
function extractTypesOfKind(type: Type, kind: TypeFlags) {
|
|
return filterType(type, t => (t.flags & kind) !== 0);
|
|
}
|
|
|
|
// Return a new type in which occurrences of the string and number primitive types in
|
|
// typeWithPrimitives have been replaced with occurrences of string literals and numeric
|
|
// literals in typeWithLiterals, respectively.
|
|
function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) {
|
|
if (isTypeSubsetOf(stringType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral) ||
|
|
isTypeSubsetOf(numberType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.NumberLiteral) ||
|
|
isTypeSubsetOf(bigintType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.BigIntLiteral)) {
|
|
return mapType(typeWithPrimitives, t =>
|
|
t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral) :
|
|
t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) :
|
|
t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t);
|
|
}
|
|
return typeWithPrimitives;
|
|
}
|
|
|
|
function isIncomplete(flowType: FlowType) {
|
|
return flowType.flags === 0;
|
|
}
|
|
|
|
function getTypeFromFlowType(flowType: FlowType) {
|
|
return flowType.flags === 0 ? (<IncompleteType>flowType).type : <Type>flowType;
|
|
}
|
|
|
|
function createFlowType(type: Type, incomplete: boolean): FlowType {
|
|
return incomplete ? { flags: 0, type } : type;
|
|
}
|
|
|
|
// An evolving array type tracks the element types that have so far been seen in an
|
|
// 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving
|
|
// array types are ultimately converted into manifest array types (using getFinalArrayType)
|
|
// and never escape the getFlowTypeOfReference function.
|
|
function createEvolvingArrayType(elementType: Type): EvolvingArrayType {
|
|
const result = <EvolvingArrayType>createObjectType(ObjectFlags.EvolvingArray);
|
|
result.elementType = elementType;
|
|
return result;
|
|
}
|
|
|
|
function getEvolvingArrayType(elementType: Type): EvolvingArrayType {
|
|
return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType));
|
|
}
|
|
|
|
// When adding evolving array element types we do not perform subtype reduction. Instead,
|
|
// we defer subtype reduction until the evolving array type is finalized into a manifest
|
|
// array type.
|
|
function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType {
|
|
const elementType = getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node));
|
|
return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType]));
|
|
}
|
|
|
|
function createFinalArrayType(elementType: Type) {
|
|
return elementType.flags & TypeFlags.Never ?
|
|
autoArrayType :
|
|
createArrayType(elementType.flags & TypeFlags.Union ?
|
|
getUnionType((<UnionType>elementType).types, UnionReduction.Subtype) :
|
|
elementType);
|
|
}
|
|
|
|
// We perform subtype reduction upon obtaining the final array type from an evolving array type.
|
|
function getFinalArrayType(evolvingArrayType: EvolvingArrayType): Type {
|
|
return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType));
|
|
}
|
|
|
|
function finalizeEvolvingArrayType(type: Type): Type {
|
|
return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType(<EvolvingArrayType>type) : type;
|
|
}
|
|
|
|
function getElementTypeOfEvolvingArrayType(type: Type) {
|
|
return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (<EvolvingArrayType>type).elementType : neverType;
|
|
}
|
|
|
|
function isEvolvingArrayTypeList(types: Type[]) {
|
|
let hasEvolvingArrayType = false;
|
|
for (const t of types) {
|
|
if (!(t.flags & TypeFlags.Never)) {
|
|
if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) {
|
|
return false;
|
|
}
|
|
hasEvolvingArrayType = true;
|
|
}
|
|
}
|
|
return hasEvolvingArrayType;
|
|
}
|
|
|
|
// At flow control branch or loop junctions, if the type along every antecedent code path
|
|
// is an evolving array type, we construct a combined evolving array type. Otherwise we
|
|
// finalize all evolving array types.
|
|
function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) {
|
|
return isEvolvingArrayTypeList(types) ?
|
|
getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) :
|
|
getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction);
|
|
}
|
|
|
|
// Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or
|
|
// 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type.
|
|
function isEvolvingArrayOperationTarget(node: Node) {
|
|
const root = getReferenceRoot(node);
|
|
const parent = root.parent;
|
|
const isLengthPushOrUnshift = isPropertyAccessExpression(parent) && (
|
|
parent.name.escapedText === "length" ||
|
|
parent.parent.kind === SyntaxKind.CallExpression
|
|
&& isIdentifier(parent.name)
|
|
&& isPushOrUnshiftIdentifier(parent.name));
|
|
const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression &&
|
|
(<ElementAccessExpression>parent).expression === root &&
|
|
parent.parent.kind === SyntaxKind.BinaryExpression &&
|
|
(<BinaryExpression>parent.parent).operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
(<BinaryExpression>parent.parent).left === parent &&
|
|
!isAssignmentTarget(parent.parent) &&
|
|
isTypeAssignableToKind(getTypeOfExpression((<ElementAccessExpression>parent).argumentExpression), TypeFlags.NumberLike);
|
|
return isLengthPushOrUnshift || isElementAssignment;
|
|
}
|
|
|
|
function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration | undefined) {
|
|
return !!(declaration && (
|
|
declaration.kind === SyntaxKind.VariableDeclaration || declaration.kind === SyntaxKind.Parameter ||
|
|
declaration.kind === SyntaxKind.PropertyDeclaration || declaration.kind === SyntaxKind.PropertySignature) &&
|
|
getEffectiveTypeAnnotationNode(declaration as VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature));
|
|
}
|
|
|
|
function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) {
|
|
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) {
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
|
|
if (isDeclarationWithExplicitTypeAnnotation(symbol.valueDeclaration)) {
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
if (diagnostic && symbol.valueDeclaration) {
|
|
addRelatedInfo(diagnostic, createDiagnosticForNode(symbol.valueDeclaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// We require the dotted function name in an assertion expression to be comprised of identifiers
|
|
// that reference function, method, class or value module symbols; or variable, property or
|
|
// parameter symbols with declarations that have explicit type annotations. Such references are
|
|
// resolvable with no possibility of triggering circularities in control flow analysis.
|
|
function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): Type | undefined {
|
|
if (!(node.flags & NodeFlags.InWithStatement)) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>node));
|
|
return getExplicitTypeOfSymbol(symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol, diagnostic);
|
|
case SyntaxKind.ThisKeyword:
|
|
return getExplicitThisType(node);
|
|
case SyntaxKind.SuperKeyword:
|
|
return checkSuperExpression(node);
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
const type = getTypeOfDottedName((<PropertyAccessExpression>node).expression, diagnostic);
|
|
const prop = type && getPropertyOfType(type, (<PropertyAccessExpression>node).name.escapedText);
|
|
return prop && getExplicitTypeOfSymbol(prop, diagnostic);
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return getTypeOfDottedName((<ParenthesizedExpression>node).expression, diagnostic);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getEffectsSignature(node: CallExpression) {
|
|
const links = getNodeLinks(node);
|
|
let signature = links.effectsSignature;
|
|
if (signature === undefined) {
|
|
// A call expression parented by an expression statement is a potential assertion. Other call
|
|
// expressions are potential type predicate function calls. In order to avoid triggering
|
|
// circularities in control flow analysis, we use getTypeOfDottedName when resolving the call
|
|
// target expression of an assertion.
|
|
let funcType: Type | undefined;
|
|
if (node.parent.kind === SyntaxKind.ExpressionStatement) {
|
|
funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined);
|
|
}
|
|
else if (node.expression.kind !== SyntaxKind.SuperKeyword) {
|
|
if (isOptionalChain(node)) {
|
|
funcType = checkNonNullType(
|
|
getOptionalExpressionType(checkExpression(node.expression), node.expression),
|
|
node.expression
|
|
);
|
|
}
|
|
else {
|
|
funcType = checkNonNullExpression(node.expression);
|
|
}
|
|
}
|
|
const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call);
|
|
const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] :
|
|
some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) :
|
|
undefined;
|
|
signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature;
|
|
}
|
|
return signature === unknownSignature ? undefined : signature;
|
|
}
|
|
|
|
function hasTypePredicateOrNeverReturnType(signature: Signature) {
|
|
return !!(getTypePredicateOfSignature(signature) ||
|
|
signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never);
|
|
}
|
|
|
|
function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) {
|
|
if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) {
|
|
return callExpression.arguments[predicate.parameterIndex];
|
|
}
|
|
const invokedExpression = skipParentheses(callExpression.expression);
|
|
return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined;
|
|
}
|
|
|
|
function reportFlowControlError(node: Node) {
|
|
const block = <Block | ModuleBlock | SourceFile>findAncestor(node, isFunctionOrModuleBlock);
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos);
|
|
diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis));
|
|
}
|
|
|
|
function isReachableFlowNode(flow: FlowNode) {
|
|
const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false);
|
|
lastFlowNode = flow;
|
|
lastFlowNodeReachable = result;
|
|
return result;
|
|
}
|
|
|
|
function isFalseExpression(expr: Expression): boolean {
|
|
const node = skipParentheses(expr);
|
|
return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && (
|
|
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((<BinaryExpression>node).left) || isFalseExpression((<BinaryExpression>node).right)) ||
|
|
(<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((<BinaryExpression>node).left) && isFalseExpression((<BinaryExpression>node).right));
|
|
}
|
|
|
|
function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean {
|
|
while (true) {
|
|
if (flow === lastFlowNode) {
|
|
return lastFlowNodeReachable;
|
|
}
|
|
const flags = flow.flags;
|
|
if (flags & FlowFlags.Shared) {
|
|
if (!noCacheCheck) {
|
|
const id = getFlowNodeId(flow);
|
|
const reachable = flowNodeReachable[id];
|
|
return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true));
|
|
}
|
|
noCacheCheck = false;
|
|
}
|
|
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) {
|
|
flow = (<FlowAssignment | FlowCondition | FlowArrayMutation | PreFinallyFlow>flow).antecedent;
|
|
}
|
|
else if (flags & FlowFlags.Call) {
|
|
const signature = getEffectsSignature((<FlowCall>flow).node);
|
|
if (signature) {
|
|
const predicate = getTypePredicateOfSignature(signature);
|
|
if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier) {
|
|
const predicateArgument = (<FlowCall>flow).node.arguments[predicate.parameterIndex];
|
|
if (predicateArgument && isFalseExpression(predicateArgument)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) {
|
|
return false;
|
|
}
|
|
}
|
|
flow = (<FlowCall>flow).antecedent;
|
|
}
|
|
else if (flags & FlowFlags.BranchLabel) {
|
|
// A branching point is reachable if any branch is reachable.
|
|
return some((<FlowLabel>flow).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false));
|
|
}
|
|
else if (flags & FlowFlags.LoopLabel) {
|
|
// A loop is reachable if the control flow path that leads to the top is reachable.
|
|
flow = (<FlowLabel>flow).antecedents![0];
|
|
}
|
|
else if (flags & FlowFlags.SwitchClause) {
|
|
// The control flow path representing an unmatched value in a switch statement with
|
|
// no default clause is unreachable if the switch statement is exhaustive.
|
|
if ((<FlowSwitchClause>flow).clauseStart === (<FlowSwitchClause>flow).clauseEnd && isExhaustiveSwitchStatement((<FlowSwitchClause>flow).switchStatement)) {
|
|
return false;
|
|
}
|
|
flow = (<FlowSwitchClause>flow).antecedent;
|
|
}
|
|
else if (flags & FlowFlags.ReduceLabel) {
|
|
// Cache is unreliable once we start adjusting labels
|
|
lastFlowNode = undefined;
|
|
const target = (<FlowReduceLabel>flow).target;
|
|
const saveAntecedents = target.antecedents;
|
|
target.antecedents = (<FlowReduceLabel>flow).antecedents;
|
|
const result = isReachableFlowNodeWorker((<FlowReduceLabel>flow).antecedent, /*noCacheCheck*/ false);
|
|
target.antecedents = saveAntecedents;
|
|
return result;
|
|
}
|
|
else {
|
|
return !(flags & FlowFlags.Unreachable);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
|
|
let key: string | undefined;
|
|
let keySet = false;
|
|
let flowDepth = 0;
|
|
if (flowAnalysisDisabled) {
|
|
return errorType;
|
|
}
|
|
if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
|
|
return declaredType;
|
|
}
|
|
flowInvocationCount++;
|
|
const sharedFlowStart = sharedFlowCount;
|
|
const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
|
|
sharedFlowCount = sharedFlowStart;
|
|
// When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation,
|
|
// we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations
|
|
// on empty arrays are possible without implicit any errors and new element types can be inferred without
|
|
// type mismatch errors.
|
|
const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType);
|
|
if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
|
|
return declaredType;
|
|
}
|
|
return resultType;
|
|
|
|
function getOrSetCacheKey() {
|
|
if (keySet) {
|
|
return key;
|
|
}
|
|
keySet = true;
|
|
return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer);
|
|
}
|
|
|
|
function getTypeAtFlowNode(flow: FlowNode): FlowType {
|
|
if (flowDepth === 2000) {
|
|
// We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error
|
|
// and disable further control flow analysis in the containing function or module body.
|
|
flowAnalysisDisabled = true;
|
|
reportFlowControlError(reference);
|
|
return errorType;
|
|
}
|
|
flowDepth++;
|
|
while (true) {
|
|
const flags = flow.flags;
|
|
if (flags & FlowFlags.Shared) {
|
|
// We cache results of flow type resolution for shared nodes that were previously visited in
|
|
// the same getFlowTypeOfReference invocation. A node is considered shared when it is the
|
|
// antecedent of more than one node.
|
|
for (let i = sharedFlowStart; i < sharedFlowCount; i++) {
|
|
if (sharedFlowNodes[i] === flow) {
|
|
flowDepth--;
|
|
return sharedFlowTypes[i];
|
|
}
|
|
}
|
|
}
|
|
let type: FlowType | undefined;
|
|
if (flags & FlowFlags.Assignment) {
|
|
type = getTypeAtFlowAssignment(<FlowAssignment>flow);
|
|
if (!type) {
|
|
flow = (<FlowAssignment>flow).antecedent;
|
|
continue;
|
|
}
|
|
}
|
|
else if (flags & FlowFlags.Call) {
|
|
type = getTypeAtFlowCall(<FlowCall>flow);
|
|
if (!type) {
|
|
flow = (<FlowCall>flow).antecedent;
|
|
continue;
|
|
}
|
|
}
|
|
else if (flags & FlowFlags.Condition) {
|
|
type = getTypeAtFlowCondition(<FlowCondition>flow);
|
|
}
|
|
else if (flags & FlowFlags.SwitchClause) {
|
|
type = getTypeAtSwitchClause(<FlowSwitchClause>flow);
|
|
}
|
|
else if (flags & FlowFlags.Label) {
|
|
if ((<FlowLabel>flow).antecedents!.length === 1) {
|
|
flow = (<FlowLabel>flow).antecedents![0];
|
|
continue;
|
|
}
|
|
type = flags & FlowFlags.BranchLabel ?
|
|
getTypeAtFlowBranchLabel(<FlowLabel>flow) :
|
|
getTypeAtFlowLoopLabel(<FlowLabel>flow);
|
|
}
|
|
else if (flags & FlowFlags.ArrayMutation) {
|
|
type = getTypeAtFlowArrayMutation(<FlowArrayMutation>flow);
|
|
if (!type) {
|
|
flow = (<FlowArrayMutation>flow).antecedent;
|
|
continue;
|
|
}
|
|
}
|
|
else if (flags & FlowFlags.ReduceLabel) {
|
|
const target = (<FlowReduceLabel>flow).target;
|
|
const saveAntecedents = target.antecedents;
|
|
target.antecedents = (<FlowReduceLabel>flow).antecedents;
|
|
type = getTypeAtFlowNode((<FlowReduceLabel>flow).antecedent);
|
|
target.antecedents = saveAntecedents;
|
|
}
|
|
else if (flags & FlowFlags.Start) {
|
|
// Check if we should continue with the control flow of the containing function.
|
|
const container = (<FlowStart>flow).node;
|
|
if (container && container !== flowContainer &&
|
|
reference.kind !== SyntaxKind.PropertyAccessExpression &&
|
|
reference.kind !== SyntaxKind.ElementAccessExpression &&
|
|
reference.kind !== SyntaxKind.ThisKeyword) {
|
|
flow = container.flowNode!;
|
|
continue;
|
|
}
|
|
// At the top of the flow we have the initial type.
|
|
type = initialType;
|
|
}
|
|
else {
|
|
// Unreachable code errors are reported in the binding phase. Here we
|
|
// simply return the non-auto declared type to reduce follow-on errors.
|
|
type = convertAutoToAny(declaredType);
|
|
}
|
|
if (flags & FlowFlags.Shared) {
|
|
// Record visited node and the associated type in the cache.
|
|
sharedFlowNodes[sharedFlowCount] = flow;
|
|
sharedFlowTypes[sharedFlowCount] = type;
|
|
sharedFlowCount++;
|
|
}
|
|
flowDepth--;
|
|
return type;
|
|
}
|
|
}
|
|
|
|
function getInitialOrAssignedType(flow: FlowAssignment) {
|
|
const node = flow.node;
|
|
return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
|
|
getInitialType(<VariableDeclaration | BindingElement>node) :
|
|
getAssignedType(node), reference);
|
|
}
|
|
|
|
function getTypeAtFlowAssignment(flow: FlowAssignment) {
|
|
const node = flow.node;
|
|
// Assignments only narrow the computed type if the declared type is a union type. Thus, we
|
|
// only need to evaluate the assigned type if the declared type is a union type.
|
|
if (isMatchingReference(reference, node)) {
|
|
if (!isReachableFlowNode(flow)) {
|
|
return unreachableNeverType;
|
|
}
|
|
if (getAssignmentTargetKind(node) === AssignmentKind.Compound) {
|
|
const flowType = getTypeAtFlowNode(flow.antecedent);
|
|
return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType));
|
|
}
|
|
if (declaredType === autoType || declaredType === autoArrayType) {
|
|
if (isEmptyArrayAssignment(node)) {
|
|
return getEvolvingArrayType(neverType);
|
|
}
|
|
const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow));
|
|
return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType;
|
|
}
|
|
if (declaredType.flags & TypeFlags.Union) {
|
|
return getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(flow));
|
|
}
|
|
return declaredType;
|
|
}
|
|
// We didn't have a direct match. However, if the reference is a dotted name, this
|
|
// may be an assignment to a left hand part of the reference. For example, for a
|
|
// reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case,
|
|
// return the declared type.
|
|
if (containsMatchingReference(reference, node)) {
|
|
if (!isReachableFlowNode(flow)) {
|
|
return unreachableNeverType;
|
|
}
|
|
// A matching dotted name might also be an expando property on a function *expression*,
|
|
// in which case we continue control flow analysis back to the function's declaration
|
|
if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) {
|
|
const init = getDeclaredExpandoInitializer(node);
|
|
if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) {
|
|
return getTypeAtFlowNode(flow.antecedent);
|
|
}
|
|
}
|
|
return declaredType;
|
|
}
|
|
// for (const _ in ref) acts as a nonnull on ref
|
|
if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) {
|
|
return getNonNullableTypeIfNeeded(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent)));
|
|
}
|
|
// Assignment doesn't affect reference
|
|
return undefined;
|
|
}
|
|
|
|
function narrowTypeByAssertion(type: Type, expr: Expression): Type {
|
|
const node = skipParentheses(expr);
|
|
if (node.kind === SyntaxKind.FalseKeyword) {
|
|
return unreachableNeverType;
|
|
}
|
|
if (node.kind === SyntaxKind.BinaryExpression) {
|
|
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
|
|
return narrowTypeByAssertion(narrowTypeByAssertion(type, (<BinaryExpression>node).left), (<BinaryExpression>node).right);
|
|
}
|
|
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken) {
|
|
return getUnionType([narrowTypeByAssertion(type, (<BinaryExpression>node).left), narrowTypeByAssertion(type, (<BinaryExpression>node).right)]);
|
|
}
|
|
}
|
|
return narrowType(type, node, /*assumeTrue*/ true);
|
|
}
|
|
|
|
function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined {
|
|
const signature = getEffectsSignature(flow.node);
|
|
if (signature) {
|
|
const predicate = getTypePredicateOfSignature(signature);
|
|
if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) {
|
|
const flowType = getTypeAtFlowNode(flow.antecedent);
|
|
const type = getTypeFromFlowType(flowType);
|
|
const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) :
|
|
predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) :
|
|
type;
|
|
return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType));
|
|
}
|
|
if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) {
|
|
return unreachableNeverType;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined {
|
|
if (declaredType === autoType || declaredType === autoArrayType) {
|
|
const node = flow.node;
|
|
const expr = node.kind === SyntaxKind.CallExpression ?
|
|
(<PropertyAccessExpression>node.expression).expression :
|
|
(<ElementAccessExpression>node.left).expression;
|
|
if (isMatchingReference(reference, getReferenceCandidate(expr))) {
|
|
const flowType = getTypeAtFlowNode(flow.antecedent);
|
|
const type = getTypeFromFlowType(flowType);
|
|
if (getObjectFlags(type) & ObjectFlags.EvolvingArray) {
|
|
let evolvedType = <EvolvingArrayType>type;
|
|
if (node.kind === SyntaxKind.CallExpression) {
|
|
for (const arg of node.arguments) {
|
|
evolvedType = addEvolvingArrayElementType(evolvedType, arg);
|
|
}
|
|
}
|
|
else {
|
|
// We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time)
|
|
const indexType = getContextFreeTypeOfExpression((<ElementAccessExpression>node.left).argumentExpression);
|
|
if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
|
|
evolvedType = addEvolvingArrayElementType(evolvedType, node.right);
|
|
}
|
|
}
|
|
return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType));
|
|
}
|
|
return flowType;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getTypeAtFlowCondition(flow: FlowCondition): FlowType {
|
|
const flowType = getTypeAtFlowNode(flow.antecedent);
|
|
const type = getTypeFromFlowType(flowType);
|
|
if (type.flags & TypeFlags.Never) {
|
|
return flowType;
|
|
}
|
|
// If we have an antecedent type (meaning we're reachable in some way), we first
|
|
// attempt to narrow the antecedent type. If that produces the never type, and if
|
|
// the antecedent type is incomplete (i.e. a transient type in a loop), then we
|
|
// take the type guard as an indication that control *could* reach here once we
|
|
// have the complete type. We proceed by switching to the silent never type which
|
|
// doesn't report errors when operators are applied to it. Note that this is the
|
|
// *only* place a silent never type is ever generated.
|
|
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
|
|
const nonEvolvingType = finalizeEvolvingArrayType(type);
|
|
const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue);
|
|
if (narrowedType === nonEvolvingType) {
|
|
return flowType;
|
|
}
|
|
const incomplete = isIncomplete(flowType);
|
|
const resultType = incomplete && narrowedType.flags & TypeFlags.Never ? silentNeverType : narrowedType;
|
|
return createFlowType(resultType, incomplete);
|
|
}
|
|
|
|
function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType {
|
|
const expr = flow.switchStatement.expression;
|
|
const flowType = getTypeAtFlowNode(flow.antecedent);
|
|
let type = getTypeFromFlowType(flowType);
|
|
if (isMatchingReference(reference, expr)) {
|
|
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
|
|
}
|
|
else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
|
|
type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
|
|
}
|
|
else {
|
|
if (strictNullChecks) {
|
|
if (optionalChainContainsReference(expr, reference)) {
|
|
type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
|
|
t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never)));
|
|
}
|
|
else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) {
|
|
type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
|
|
t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (<StringLiteralType>t).value === "undefined"));
|
|
}
|
|
}
|
|
if (isMatchingReferenceDiscriminant(expr, type)) {
|
|
type = narrowTypeByDiscriminant(type, expr as AccessExpression,
|
|
t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
|
|
}
|
|
}
|
|
return createFlowType(type, isIncomplete(flowType));
|
|
}
|
|
|
|
function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
|
|
const antecedentTypes: Type[] = [];
|
|
let subtypeReduction = false;
|
|
let seenIncomplete = false;
|
|
let bypassFlow: FlowSwitchClause | undefined;
|
|
for (const antecedent of flow.antecedents!) {
|
|
if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (<FlowSwitchClause>antecedent).clauseStart === (<FlowSwitchClause>antecedent).clauseEnd) {
|
|
// The antecedent is the bypass branch of a potentially exhaustive switch statement.
|
|
bypassFlow = <FlowSwitchClause>antecedent;
|
|
continue;
|
|
}
|
|
const flowType = getTypeAtFlowNode(antecedent);
|
|
const type = getTypeFromFlowType(flowType);
|
|
// If the type at a particular antecedent path is the declared type and the
|
|
// reference is known to always be assigned (i.e. when declared and initial types
|
|
// are the same), there is no reason to process more antecedents since the only
|
|
// possible outcome is subtypes that will be removed in the final union type anyway.
|
|
if (type === declaredType && declaredType === initialType) {
|
|
return type;
|
|
}
|
|
pushIfUnique(antecedentTypes, type);
|
|
// If an antecedent type is not a subset of the declared type, we need to perform
|
|
// subtype reduction. This happens when a "foreign" type is injected into the control
|
|
// flow using the instanceof operator or a user defined type predicate.
|
|
if (!isTypeSubsetOf(type, declaredType)) {
|
|
subtypeReduction = true;
|
|
}
|
|
if (isIncomplete(flowType)) {
|
|
seenIncomplete = true;
|
|
}
|
|
}
|
|
if (bypassFlow) {
|
|
const flowType = getTypeAtFlowNode(bypassFlow);
|
|
const type = getTypeFromFlowType(flowType);
|
|
// If the bypass flow contributes a type we haven't seen yet and the switch statement
|
|
// isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase
|
|
// the risk of circularities, we only want to perform them when they make a difference.
|
|
if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) {
|
|
if (type === declaredType && declaredType === initialType) {
|
|
return type;
|
|
}
|
|
antecedentTypes.push(type);
|
|
if (!isTypeSubsetOf(type, declaredType)) {
|
|
subtypeReduction = true;
|
|
}
|
|
if (isIncomplete(flowType)) {
|
|
seenIncomplete = true;
|
|
}
|
|
}
|
|
}
|
|
return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete);
|
|
}
|
|
|
|
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
|
|
// If we have previously computed the control flow type for the reference at
|
|
// this flow loop junction, return the cached type.
|
|
const id = getFlowNodeId(flow);
|
|
const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap<Type>());
|
|
const key = getOrSetCacheKey();
|
|
if (!key) {
|
|
// No cache key is generated when binding patterns are in unnarrowable situations
|
|
return declaredType;
|
|
}
|
|
const cached = cache.get(key);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
// If this flow loop junction and reference are already being processed, return
|
|
// the union of the types computed for each branch so far, marked as incomplete.
|
|
// It is possible to see an empty array in cases where loops are nested and the
|
|
// back edge of the outer loop reaches an inner loop that is already being analyzed.
|
|
// In such cases we restart the analysis of the inner loop, which will then see
|
|
// a non-empty in-process array for the outer loop and eventually terminate because
|
|
// the first antecedent of a loop junction is always the non-looping control flow
|
|
// path that leads to the top.
|
|
for (let i = flowLoopStart; i < flowLoopCount; i++) {
|
|
if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) {
|
|
return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true);
|
|
}
|
|
}
|
|
// Add the flow loop junction and reference to the in-process stack and analyze
|
|
// each antecedent code path.
|
|
const antecedentTypes: Type[] = [];
|
|
let subtypeReduction = false;
|
|
let firstAntecedentType: FlowType | undefined;
|
|
for (const antecedent of flow.antecedents!) {
|
|
let flowType;
|
|
if (!firstAntecedentType) {
|
|
// The first antecedent of a loop junction is always the non-looping control
|
|
// flow path that leads to the top.
|
|
flowType = firstAntecedentType = getTypeAtFlowNode(antecedent);
|
|
}
|
|
else {
|
|
// All but the first antecedent are the looping control flow paths that lead
|
|
// back to the loop junction. We track these on the flow loop stack.
|
|
flowLoopNodes[flowLoopCount] = flow;
|
|
flowLoopKeys[flowLoopCount] = key;
|
|
flowLoopTypes[flowLoopCount] = antecedentTypes;
|
|
flowLoopCount++;
|
|
const saveFlowTypeCache = flowTypeCache;
|
|
flowTypeCache = undefined;
|
|
flowType = getTypeAtFlowNode(antecedent);
|
|
flowTypeCache = saveFlowTypeCache;
|
|
flowLoopCount--;
|
|
// If we see a value appear in the cache it is a sign that control flow analysis
|
|
// was restarted and completed by checkExpressionCached. We can simply pick up
|
|
// the resulting type and bail out.
|
|
const cached = cache.get(key);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
}
|
|
const type = getTypeFromFlowType(flowType);
|
|
pushIfUnique(antecedentTypes, type);
|
|
// If an antecedent type is not a subset of the declared type, we need to perform
|
|
// subtype reduction. This happens when a "foreign" type is injected into the control
|
|
// flow using the instanceof operator or a user defined type predicate.
|
|
if (!isTypeSubsetOf(type, declaredType)) {
|
|
subtypeReduction = true;
|
|
}
|
|
// If the type at a particular antecedent path is the declared type there is no
|
|
// reason to process more antecedents since the only possible outcome is subtypes
|
|
// that will be removed in the final union type anyway.
|
|
if (type === declaredType) {
|
|
break;
|
|
}
|
|
}
|
|
// The result is incomplete if the first antecedent (the non-looping control flow path)
|
|
// is incomplete.
|
|
const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal);
|
|
if (isIncomplete(firstAntecedentType!)) {
|
|
return createFlowType(result, /*incomplete*/ true);
|
|
}
|
|
cache.set(key, result);
|
|
return result;
|
|
}
|
|
|
|
function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
|
|
if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) {
|
|
return false;
|
|
}
|
|
const name = getAccessedPropertyName(expr);
|
|
if (name === undefined) {
|
|
return false;
|
|
}
|
|
return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name);
|
|
}
|
|
|
|
function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type {
|
|
const propName = getAccessedPropertyName(access);
|
|
if (propName === undefined) {
|
|
return type;
|
|
}
|
|
const propType = getTypeOfPropertyOfType(type, propName);
|
|
if (!propType) {
|
|
return type;
|
|
}
|
|
const narrowedPropType = narrowType(propType);
|
|
return filterType(type, t => {
|
|
const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName);
|
|
return !(discriminantType.flags & TypeFlags.Never) && isTypeComparableTo(discriminantType, narrowedPropType);
|
|
});
|
|
}
|
|
|
|
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
|
|
if (isMatchingReference(reference, expr)) {
|
|
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
|
|
}
|
|
if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
|
|
type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
|
|
}
|
|
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
|
|
return narrowTypeByDiscriminant(type, <AccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) {
|
|
if (getIndexInfoOfType(type, IndexKind.String)) {
|
|
return true;
|
|
}
|
|
const prop = getPropertyOfType(type, propName);
|
|
if (prop) {
|
|
return prop.flags & SymbolFlags.Optional ? true : assumeTrue;
|
|
}
|
|
return !assumeTrue;
|
|
}
|
|
|
|
function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) {
|
|
if (type.flags & (TypeFlags.Union | TypeFlags.Object) || isThisTypeParameter(type)) {
|
|
const propName = escapeLeadingUnderscores(literal.text);
|
|
return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue));
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
|
|
switch (expr.operatorToken.kind) {
|
|
case SyntaxKind.EqualsToken:
|
|
return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue);
|
|
case SyntaxKind.EqualsEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsToken:
|
|
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
const operator = expr.operatorToken.kind;
|
|
const left = getReferenceCandidate(expr.left);
|
|
const right = getReferenceCandidate(expr.right);
|
|
if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) {
|
|
return narrowTypeByTypeof(type, <TypeOfExpression>left, operator, right, assumeTrue);
|
|
}
|
|
if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
|
|
return narrowTypeByTypeof(type, <TypeOfExpression>right, operator, left, assumeTrue);
|
|
}
|
|
if (isConstructorAccessExpression(left)) {
|
|
return narrowTypeByConstructor(type, left, operator, right, assumeTrue);
|
|
}
|
|
if (isConstructorAccessExpression(right)) {
|
|
return narrowTypeByConstructor(type, right, operator, left, assumeTrue);
|
|
}
|
|
if (isMatchingReference(reference, left)) {
|
|
return narrowTypeByEquality(type, operator, right, assumeTrue);
|
|
}
|
|
if (isMatchingReference(reference, right)) {
|
|
return narrowTypeByEquality(type, operator, left, assumeTrue);
|
|
}
|
|
if (strictNullChecks) {
|
|
if (optionalChainContainsReference(left, reference)) {
|
|
type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue);
|
|
}
|
|
else if (optionalChainContainsReference(right, reference)) {
|
|
type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue);
|
|
}
|
|
}
|
|
if (isMatchingReferenceDiscriminant(left, declaredType)) {
|
|
return narrowTypeByDiscriminant(type, <AccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
|
|
}
|
|
if (isMatchingReferenceDiscriminant(right, declaredType)) {
|
|
return narrowTypeByDiscriminant(type, <AccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
|
|
}
|
|
break;
|
|
case SyntaxKind.InstanceOfKeyword:
|
|
return narrowTypeByInstanceof(type, expr, assumeTrue);
|
|
case SyntaxKind.InKeyword:
|
|
const target = getReferenceCandidate(expr.right);
|
|
if (isStringLiteralLike(expr.left) && isMatchingReference(reference, target)) {
|
|
return narrowByInKeyword(type, expr.left, assumeTrue);
|
|
}
|
|
break;
|
|
case SyntaxKind.CommaToken:
|
|
return narrowType(type, expr.right, assumeTrue);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
|
|
// We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows:
|
|
// When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch.
|
|
// When operator is !== and type of value excludes undefined, null and undefined is removed from type of obj in false branch.
|
|
// When operator is == and type of value excludes null and undefined, null and undefined is removed from type of obj in true branch.
|
|
// When operator is != and type of value excludes null and undefined, null and undefined is removed from type of obj in false branch.
|
|
// When operator is === and type of value is undefined, null and undefined is removed from type of obj in false branch.
|
|
// When operator is !== and type of value is undefined, null and undefined is removed from type of obj in true branch.
|
|
// When operator is == and type of value is null or undefined, null and undefined is removed from type of obj in false branch.
|
|
// When operator is != and type of value is null or undefined, null and undefined is removed from type of obj in true branch.
|
|
const equalsOperator = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
|
|
const nullableFlags = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? TypeFlags.Nullable : TypeFlags.Undefined;
|
|
const valueType = getTypeOfExpression(value);
|
|
// Note that we include any and unknown in the exclusion test because their domain includes null and undefined.
|
|
const removeNullable = equalsOperator !== assumeTrue && everyType(valueType, t => !!(t.flags & nullableFlags)) ||
|
|
equalsOperator === assumeTrue && everyType(valueType, t => !(t.flags & (TypeFlags.AnyOrUnknown | nullableFlags)));
|
|
return removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
|
|
}
|
|
|
|
function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
|
|
if (type.flags & TypeFlags.Any) {
|
|
return type;
|
|
}
|
|
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
assumeTrue = !assumeTrue;
|
|
}
|
|
const valueType = getTypeOfExpression(value);
|
|
if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) {
|
|
if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
|
|
return valueType;
|
|
}
|
|
if (valueType.flags & TypeFlags.Object) {
|
|
return nonPrimitiveType;
|
|
}
|
|
return type;
|
|
}
|
|
if (valueType.flags & TypeFlags.Nullable) {
|
|
if (!strictNullChecks) {
|
|
return type;
|
|
}
|
|
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
|
|
const facts = doubleEquals ?
|
|
assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
|
|
valueType.flags & TypeFlags.Null ?
|
|
assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
|
|
assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
|
|
return getTypeWithFacts(type, facts);
|
|
}
|
|
if (type.flags & TypeFlags.NotUnionOrUnit) {
|
|
return type;
|
|
}
|
|
if (assumeTrue) {
|
|
const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ?
|
|
(t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType)) :
|
|
t => areTypesComparable(t, valueType);
|
|
const narrowedType = filterType(type, filterFn);
|
|
return narrowedType.flags & TypeFlags.Never ? type : replacePrimitivesWithLiterals(narrowedType, valueType);
|
|
}
|
|
if (isUnitType(valueType)) {
|
|
const regularType = getRegularTypeOfLiteralType(valueType);
|
|
return filterType(type, t => getRegularTypeOfLiteralType(t) !== regularType);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type {
|
|
// We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands
|
|
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
assumeTrue = !assumeTrue;
|
|
}
|
|
const target = getReferenceCandidate(typeOfExpr.expression);
|
|
if (!isMatchingReference(reference, target)) {
|
|
if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) {
|
|
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
|
|
}
|
|
return type;
|
|
}
|
|
if (type.flags & TypeFlags.Any && literal.text === "function") {
|
|
return type;
|
|
}
|
|
const facts = assumeTrue ?
|
|
typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
|
|
typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
|
|
return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts);
|
|
|
|
function narrowTypeForTypeof(type: Type) {
|
|
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
|
|
return getUnionType([nonPrimitiveType, nullType]);
|
|
}
|
|
// We narrow a non-union type to an exact primitive type if the non-union type
|
|
// is a supertype of that primitive type. For example, type 'any' can be narrowed
|
|
// to one of the primitive types.
|
|
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
|
|
if (targetType) {
|
|
if (isTypeSubtypeOf(type, targetType)) {
|
|
return type;
|
|
}
|
|
if (isTypeSubtypeOf(targetType, type)) {
|
|
return targetType;
|
|
}
|
|
if (type.flags & TypeFlags.Instantiable) {
|
|
const constraint = getBaseConstraintOfType(type) || anyType;
|
|
if (isTypeSubtypeOf(targetType, constraint)) {
|
|
return getIntersectionType([type, targetType]);
|
|
}
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
}
|
|
|
|
function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) {
|
|
const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck);
|
|
return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
|
|
}
|
|
|
|
function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
|
|
// We only narrow if all case expressions specify
|
|
// values with unit types, except for the case where
|
|
// `type` is unknown. In this instance we map object
|
|
// types to the nonPrimitive type and narrow with that.
|
|
const switchTypes = getSwitchClauseTypes(switchStatement);
|
|
if (!switchTypes.length) {
|
|
return type;
|
|
}
|
|
const clauseTypes = switchTypes.slice(clauseStart, clauseEnd);
|
|
const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType);
|
|
if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) {
|
|
let groundClauseTypes: Type[] | undefined;
|
|
for (let i = 0; i < clauseTypes.length; i += 1) {
|
|
const t = clauseTypes[i];
|
|
if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
|
|
if (groundClauseTypes !== undefined) {
|
|
groundClauseTypes.push(t);
|
|
}
|
|
}
|
|
else if (t.flags & TypeFlags.Object) {
|
|
if (groundClauseTypes === undefined) {
|
|
groundClauseTypes = clauseTypes.slice(0, i);
|
|
}
|
|
groundClauseTypes.push(nonPrimitiveType);
|
|
}
|
|
else {
|
|
return type;
|
|
}
|
|
}
|
|
return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes);
|
|
}
|
|
const discriminantType = getUnionType(clauseTypes);
|
|
const caseType =
|
|
discriminantType.flags & TypeFlags.Never ? neverType :
|
|
replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType);
|
|
if (!hasDefaultClause) {
|
|
return caseType;
|
|
}
|
|
const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, getRegularTypeOfLiteralType(t))));
|
|
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
|
|
}
|
|
|
|
function getImpliedTypeFromTypeofCase(type: Type, text: string) {
|
|
switch (text) {
|
|
case "function":
|
|
return type.flags & TypeFlags.Any ? type : globalFunctionType;
|
|
case "object":
|
|
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
|
|
default:
|
|
return typeofTypesByName.get(text) || type;
|
|
}
|
|
}
|
|
|
|
function narrowTypeForTypeofSwitch(candidate: Type) {
|
|
return (type: Type) => {
|
|
if (isTypeSubtypeOf(candidate, type)) {
|
|
return candidate;
|
|
}
|
|
if (type.flags & TypeFlags.Instantiable) {
|
|
const constraint = getBaseConstraintOfType(type) || anyType;
|
|
if (isTypeSubtypeOf(candidate, constraint)) {
|
|
return getIntersectionType([type, candidate]);
|
|
}
|
|
}
|
|
return type;
|
|
};
|
|
}
|
|
|
|
function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
|
|
const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement, /*retainDefault*/ true);
|
|
if (!switchWitnesses.length) {
|
|
return type;
|
|
}
|
|
// Equal start and end denotes implicit fallthrough; undefined marks explicit default clause
|
|
const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined);
|
|
const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd);
|
|
let clauseWitnesses: string[];
|
|
let switchFacts: TypeFacts;
|
|
if (defaultCaseLocation > -1) {
|
|
// We no longer need the undefined denoting an
|
|
// explicit default case. Remove the undefined and
|
|
// fix-up clauseStart and clauseEnd. This means
|
|
// that we don't have to worry about undefined
|
|
// in the witness array.
|
|
const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
|
|
// The adjusted clause start and end after removing the `default` statement.
|
|
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
|
|
const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
|
|
clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
|
|
switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause);
|
|
}
|
|
else {
|
|
clauseWitnesses = <string[]>switchWitnesses.slice(clauseStart, clauseEnd);
|
|
switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, <string[]>switchWitnesses, hasDefaultClause);
|
|
}
|
|
if (hasDefaultClause) {
|
|
return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
|
|
}
|
|
/*
|
|
The implied type is the raw type suggested by a
|
|
value being caught in this clause.
|
|
|
|
When the clause contains a default case we ignore
|
|
the implied type and try to narrow using any facts
|
|
we can learn: see `switchFacts`.
|
|
|
|
Example:
|
|
switch (typeof x) {
|
|
case 'number':
|
|
case 'string': break;
|
|
default: break;
|
|
case 'number':
|
|
case 'boolean': break
|
|
}
|
|
|
|
In the first clause (case `number` and `string`) the
|
|
implied type is number | string.
|
|
|
|
In the default clause we de not compute an implied type.
|
|
|
|
In the third clause (case `number` and `boolean`)
|
|
the naive implied type is number | boolean, however
|
|
we use the type facts to narrow the implied type to
|
|
boolean. We know that number cannot be selected
|
|
because it is caught in the first clause.
|
|
*/
|
|
let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofCase(type, text))), switchFacts);
|
|
if (impliedType.flags & TypeFlags.Union) {
|
|
impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
|
|
}
|
|
return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
|
|
}
|
|
|
|
function narrowTypeByConstructor(type: Type, constructorAccessExpr: AccessExpression, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type {
|
|
// Do not narrow when checking inequality.
|
|
if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) {
|
|
return type;
|
|
}
|
|
|
|
// In the case of `x.y`, a `x.constructor === T` type guard resets the narrowed type of `y` to its declared type.
|
|
if (!isMatchingReference(reference, constructorAccessExpr.expression)) {
|
|
return declaredType;
|
|
}
|
|
|
|
// Get the type of the constructor identifier expression, if it is not a function then do not narrow.
|
|
const identifierType = getTypeOfExpression(identifier);
|
|
if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) {
|
|
return type;
|
|
}
|
|
|
|
// Get the prototype property of the type identifier so we can find out its type.
|
|
const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String);
|
|
if (!prototypeProperty) {
|
|
return type;
|
|
}
|
|
|
|
// Get the type of the prototype, if it is undefined, or the global `Object` or `Function` types then do not narrow.
|
|
const prototypeType = getTypeOfSymbol(prototypeProperty);
|
|
const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined;
|
|
if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) {
|
|
return type;
|
|
}
|
|
|
|
// If the type that is being narrowed is `any` then just return the `candidate` type since every type is a subtype of `any`.
|
|
if (isTypeAny(type)) {
|
|
return candidate;
|
|
}
|
|
|
|
// Filter out types that are not considered to be "constructed by" the `candidate` type.
|
|
return filterType(type, t => isConstructedBy(t, candidate));
|
|
|
|
function isConstructedBy(source: Type, target: Type) {
|
|
// If either the source or target type are a class type then we need to check that they are the same exact type.
|
|
// This is because you may have a class `A` that defines some set of properties, and another class `B`
|
|
// that defines the same set of properties as class `A`, in that case they are structurally the same
|
|
// type, but when you do something like `instanceOfA.constructor === B` it will return false.
|
|
if (source.flags & TypeFlags.Object && getObjectFlags(source) & ObjectFlags.Class ||
|
|
target.flags & TypeFlags.Object && getObjectFlags(target) & ObjectFlags.Class) {
|
|
return source.symbol === target.symbol;
|
|
}
|
|
|
|
// For all other types just check that the `source` type is a subtype of the `target` type.
|
|
return isTypeSubtypeOf(source, target);
|
|
}
|
|
}
|
|
|
|
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
|
|
const left = getReferenceCandidate(expr.left);
|
|
if (!isMatchingReference(reference, left)) {
|
|
if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) {
|
|
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
// Check that right operand is a function type with a prototype property
|
|
const rightType = getTypeOfExpression(expr.right);
|
|
if (!isTypeDerivedFrom(rightType, globalFunctionType)) {
|
|
return type;
|
|
}
|
|
|
|
let targetType: Type | undefined;
|
|
const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String);
|
|
if (prototypeProperty) {
|
|
// Target type is type of the prototype property
|
|
const prototypePropertyType = getTypeOfSymbol(prototypeProperty);
|
|
if (!isTypeAny(prototypePropertyType)) {
|
|
targetType = prototypePropertyType;
|
|
}
|
|
}
|
|
|
|
// Don't narrow from 'any' if the target type is exactly 'Object' or 'Function'
|
|
if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) {
|
|
return type;
|
|
}
|
|
|
|
if (!targetType) {
|
|
const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
|
|
targetType = constructSignatures.length ?
|
|
getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) :
|
|
emptyObjectType;
|
|
}
|
|
|
|
return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
|
|
}
|
|
|
|
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) {
|
|
if (!assumeTrue) {
|
|
return filterType(type, t => !isRelated(t, candidate));
|
|
}
|
|
// If the current type is a union type, remove all constituents that couldn't be instances of
|
|
// the candidate type. If one or more constituents remain, return a union of those.
|
|
if (type.flags & TypeFlags.Union) {
|
|
const assignableType = filterType(type, t => isRelated(t, candidate));
|
|
if (!(assignableType.flags & TypeFlags.Never)) {
|
|
return assignableType;
|
|
}
|
|
}
|
|
// If the candidate type is a subtype of the target type, narrow to the candidate type.
|
|
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
|
|
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
|
|
// type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
|
|
// two types.
|
|
return isTypeSubtypeOf(candidate, type) ? candidate :
|
|
isTypeAssignableTo(type, candidate) ? type :
|
|
isTypeAssignableTo(candidate, type) ? candidate :
|
|
getIntersectionType([type, candidate]);
|
|
}
|
|
|
|
function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
|
|
if (hasMatchingArgument(callExpression, reference)) {
|
|
const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined;
|
|
const predicate = signature && getTypePredicateOfSignature(signature);
|
|
if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) {
|
|
return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue);
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type {
|
|
// Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function'
|
|
if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) {
|
|
const predicateArgument = getTypePredicateArgument(predicate, callExpression);
|
|
if (predicateArgument) {
|
|
if (isMatchingReference(reference, predicateArgument)) {
|
|
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
|
|
}
|
|
if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) &&
|
|
!(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
|
|
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
|
|
}
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
// Narrow the given type based on the given expression having the assumed boolean value. The returned type
|
|
// will be a subtype or the same type as the argument.
|
|
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
|
|
// for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a`
|
|
if (isExpressionOfOptionalChainRoot(expr) ||
|
|
isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) {
|
|
return narrowTypeByOptionality(type, expr, assumeTrue);
|
|
}
|
|
switch (expr.kind) {
|
|
case SyntaxKind.Identifier:
|
|
case SyntaxKind.ThisKeyword:
|
|
case SyntaxKind.SuperKeyword:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
return narrowTypeByTruthiness(type, expr, assumeTrue);
|
|
case SyntaxKind.CallExpression:
|
|
return narrowTypeByCallExpression(type, <CallExpression>expr, assumeTrue);
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return narrowType(type, (<ParenthesizedExpression>expr).expression, assumeTrue);
|
|
case SyntaxKind.BinaryExpression:
|
|
return narrowTypeByBinaryExpression(type, <BinaryExpression>expr, assumeTrue);
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
if ((<PrefixUnaryExpression>expr).operator === SyntaxKind.ExclamationToken) {
|
|
return narrowType(type, (<PrefixUnaryExpression>expr).operand, !assumeTrue);
|
|
}
|
|
break;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type {
|
|
if (isMatchingReference(reference, expr)) {
|
|
return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull);
|
|
}
|
|
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
|
|
return narrowTypeByDiscriminant(type, <AccessExpression>expr, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull));
|
|
}
|
|
return type;
|
|
}
|
|
}
|
|
|
|
function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) {
|
|
symbol = symbol.exportSymbol || symbol;
|
|
|
|
// If we have an identifier or a property access at the given location, if the location is
|
|
// an dotted name expression, and if the location is not an assignment target, obtain the type
|
|
// of the expression (which will reflect control flow analysis). If the expression indeed
|
|
// resolved to the given symbol, return the narrowed type.
|
|
if (location.kind === SyntaxKind.Identifier) {
|
|
if (isRightSideOfQualifiedNameOrPropertyAccess(location)) {
|
|
location = location.parent;
|
|
}
|
|
if (isExpressionNode(location) && !isAssignmentTarget(location)) {
|
|
const type = getTypeOfExpression(<Expression>location);
|
|
if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) {
|
|
return type;
|
|
}
|
|
}
|
|
}
|
|
// The location isn't a reference to the given symbol, meaning we're being asked
|
|
// a hypothetical question of what type the symbol would have if there was a reference
|
|
// to it at the given location. Since we have no control flow information for the
|
|
// hypothetical reference (control flow information is created and attached by the
|
|
// binder), we simply return the declared type of the symbol.
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
|
|
function getControlFlowContainer(node: Node): Node {
|
|
return findAncestor(node.parent, node =>
|
|
isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) ||
|
|
node.kind === SyntaxKind.ModuleBlock ||
|
|
node.kind === SyntaxKind.SourceFile ||
|
|
node.kind === SyntaxKind.PropertyDeclaration)!;
|
|
}
|
|
|
|
// Check if a parameter is assigned anywhere within its declaring function.
|
|
function isParameterAssigned(symbol: Symbol) {
|
|
const func = <FunctionLikeDeclaration>getRootDeclaration(symbol.valueDeclaration).parent;
|
|
const links = getNodeLinks(func);
|
|
if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) {
|
|
links.flags |= NodeCheckFlags.AssignmentsMarked;
|
|
if (!hasParentWithAssignmentsMarked(func)) {
|
|
markParameterAssignments(func);
|
|
}
|
|
}
|
|
return symbol.isAssigned || false;
|
|
}
|
|
|
|
function hasParentWithAssignmentsMarked(node: Node) {
|
|
return !!findAncestor(node.parent, node => isFunctionLike(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
|
|
}
|
|
|
|
function markParameterAssignments(node: Node) {
|
|
if (node.kind === SyntaxKind.Identifier) {
|
|
if (isAssignmentTarget(node)) {
|
|
const symbol = getResolvedSymbol(<Identifier>node);
|
|
if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) {
|
|
symbol.isAssigned = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
forEachChild(node, markParameterAssignments);
|
|
}
|
|
}
|
|
|
|
function isConstVariable(symbol: Symbol) {
|
|
return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 && getTypeOfSymbol(symbol) !== autoArrayType;
|
|
}
|
|
|
|
/** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */
|
|
function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type {
|
|
if (pushTypeResolution(declaration.symbol, TypeSystemPropertyName.DeclaredType)) {
|
|
const annotationIncludesUndefined = strictNullChecks &&
|
|
declaration.kind === SyntaxKind.Parameter &&
|
|
declaration.initializer &&
|
|
getFalsyFlags(declaredType) & TypeFlags.Undefined &&
|
|
!(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined);
|
|
popTypeResolution();
|
|
|
|
return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType;
|
|
}
|
|
else {
|
|
reportCircularityError(declaration.symbol);
|
|
return declaredType;
|
|
}
|
|
}
|
|
|
|
function isConstraintPosition(node: Node) {
|
|
const parent = node.parent;
|
|
return parent.kind === SyntaxKind.PropertyAccessExpression ||
|
|
parent.kind === SyntaxKind.CallExpression && (<CallExpression>parent).expression === node ||
|
|
parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>parent).expression === node ||
|
|
parent.kind === SyntaxKind.BindingElement && (<BindingElement>parent).name === node && !!(<BindingElement>parent).initializer;
|
|
}
|
|
|
|
function typeHasNullableConstraint(type: Type) {
|
|
return type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable);
|
|
}
|
|
|
|
function getConstraintForLocation(type: Type, node: Node): Type;
|
|
function getConstraintForLocation(type: Type | undefined, node: Node): Type | undefined;
|
|
function getConstraintForLocation(type: Type, node: Node): Type | undefined {
|
|
// When a node is the left hand expression of a property access, element access, or call expression,
|
|
// and the type of the node includes type variables with constraints that are nullable, we fetch the
|
|
// apparent type of the node *before* performing control flow analysis such that narrowings apply to
|
|
// the constraint type.
|
|
if (type && isConstraintPosition(node) && forEachType(type, typeHasNullableConstraint)) {
|
|
return mapType(getWidenedType(type), getBaseConstraintOrType);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function isExportOrExportExpression(location: Node) {
|
|
return !!findAncestor(location, e => e.parent && isExportAssignment(e.parent) && e.parent.expression === e && isEntityNameExpression(e));
|
|
}
|
|
|
|
function markAliasReferenced(symbol: Symbol, location: Node) {
|
|
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol)) {
|
|
if (compilerOptions.preserveConstEnums && isExportOrExportExpression(location) || !isConstEnumOrConstEnumOnlyModule(resolveAlias(symbol))) {
|
|
markAliasSymbolAsReferenced(symbol);
|
|
}
|
|
else {
|
|
markConstEnumAliasAsReferenced(symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkIdentifier(node: Identifier): Type {
|
|
const symbol = getResolvedSymbol(node);
|
|
if (symbol === unknownSymbol) {
|
|
return errorType;
|
|
}
|
|
|
|
// As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects.
|
|
// Although in down-level emit of arrow function, we emit it using function expression which means that
|
|
// arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects
|
|
// will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior.
|
|
// To avoid that we will give an error to users if they use arguments objects in arrow function so that they
|
|
// can explicitly bound arguments objects
|
|
if (symbol === argumentsSymbol) {
|
|
const container = getContainingFunction(node)!;
|
|
if (languageVersion < ScriptTarget.ES2015) {
|
|
if (container.kind === SyntaxKind.ArrowFunction) {
|
|
error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression);
|
|
}
|
|
else if (hasModifier(container, ModifierFlags.Async)) {
|
|
error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method);
|
|
}
|
|
}
|
|
|
|
getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments;
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
|
|
// We should only mark aliases as referenced if there isn't a local value declaration
|
|
// for the symbol. Also, don't mark any property access expression LHS - checkPropertyAccessExpression will handle that
|
|
if (!(node.parent && isPropertyAccessExpression(node.parent) && node.parent.expression === node)) {
|
|
markAliasReferenced(symbol, node);
|
|
}
|
|
|
|
const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
|
|
let declaration: Declaration | undefined = localOrExportSymbol.valueDeclaration;
|
|
|
|
if (localOrExportSymbol.flags & SymbolFlags.Class) {
|
|
// Due to the emit for class decorators, any reference to the class from inside of the class body
|
|
// must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
|
|
// behavior of class names in ES6.
|
|
if (declaration.kind === SyntaxKind.ClassDeclaration
|
|
&& nodeIsDecorated(declaration as ClassDeclaration)) {
|
|
let container = getContainingClass(node);
|
|
while (container !== undefined) {
|
|
if (container === declaration && container.name !== node) {
|
|
getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference;
|
|
getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass;
|
|
break;
|
|
}
|
|
|
|
container = getContainingClass(container);
|
|
}
|
|
}
|
|
else if (declaration.kind === SyntaxKind.ClassExpression) {
|
|
// When we emit a class expression with static members that contain a reference
|
|
// to the constructor in the initializer, we will need to substitute that
|
|
// binding with an alias as the class name is not in scope.
|
|
let container = getThisContainer(node, /*includeArrowFunctions*/ false);
|
|
while (container.kind !== SyntaxKind.SourceFile) {
|
|
if (container.parent === declaration) {
|
|
if (container.kind === SyntaxKind.PropertyDeclaration && hasModifier(container, ModifierFlags.Static)) {
|
|
getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference;
|
|
getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass;
|
|
}
|
|
break;
|
|
}
|
|
|
|
container = getThisContainer(container, /*includeArrowFunctions*/ false);
|
|
}
|
|
}
|
|
}
|
|
|
|
checkNestedBlockScopedBinding(node, symbol);
|
|
|
|
const type = getConstraintForLocation(getTypeOfSymbol(localOrExportSymbol), node);
|
|
const assignmentKind = getAssignmentTargetKind(node);
|
|
|
|
if (assignmentKind) {
|
|
if (!(localOrExportSymbol.flags & SymbolFlags.Variable) &&
|
|
!(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) {
|
|
error(node, Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable, symbolToString(symbol));
|
|
return errorType;
|
|
}
|
|
if (isReadonlySymbol(localOrExportSymbol)) {
|
|
if (localOrExportSymbol.flags & SymbolFlags.Variable) {
|
|
error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol));
|
|
}
|
|
else {
|
|
error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol));
|
|
}
|
|
return errorType;
|
|
}
|
|
}
|
|
|
|
const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias;
|
|
|
|
// We only narrow variables and parameters occurring in a non-assignment position. For all other
|
|
// entities we simply return the declared type.
|
|
if (localOrExportSymbol.flags & SymbolFlags.Variable) {
|
|
if (assignmentKind === AssignmentKind.Definite) {
|
|
return type;
|
|
}
|
|
}
|
|
else if (isAlias) {
|
|
declaration = find<Declaration>(symbol.declarations, isSomeImportDeclaration);
|
|
}
|
|
else {
|
|
return type;
|
|
}
|
|
|
|
if (!declaration) {
|
|
return type;
|
|
}
|
|
|
|
// The declaration container is the innermost function that encloses the declaration of the variable
|
|
// or parameter. The flow container is the innermost function starting with which we analyze the control
|
|
// flow graph to determine the control flow based type.
|
|
const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
|
|
const declarationContainer = getControlFlowContainer(declaration);
|
|
let flowContainer = getControlFlowContainer(node);
|
|
const isOuterVariable = flowContainer !== declarationContainer;
|
|
const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent);
|
|
const isModuleExports = symbol.flags & SymbolFlags.ModuleExports;
|
|
// When the control flow originates in a function expression or arrow function and we are referencing
|
|
// a const variable or parameter from an outer function, we extend the origin of the control flow
|
|
// analysis to include the immediately enclosing function.
|
|
while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
|
|
flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer)) &&
|
|
(isConstVariable(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) {
|
|
flowContainer = getControlFlowContainer(flowContainer);
|
|
}
|
|
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
|
|
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
|
|
// declaration container are the same).
|
|
const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isBindingElement(declaration) ||
|
|
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 ||
|
|
isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
|
|
node.parent.kind === SyntaxKind.NonNullExpression ||
|
|
declaration.kind === SyntaxKind.VariableDeclaration && (<VariableDeclaration>declaration).exclamationToken ||
|
|
declaration.flags & NodeFlags.Ambient;
|
|
const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) :
|
|
type === autoType || type === autoArrayType ? undefinedType :
|
|
getOptionalType(type);
|
|
const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized);
|
|
// A variable is considered uninitialized when it is possible to analyze the entire control flow graph
|
|
// from declaration to use, and when the variable's declared type doesn't include undefined but the
|
|
// control flow based type does include undefined.
|
|
if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) {
|
|
if (flowType === autoType || flowType === autoArrayType) {
|
|
if (noImplicitAny) {
|
|
error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType));
|
|
error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType));
|
|
}
|
|
return convertAutoToAny(flowType);
|
|
}
|
|
}
|
|
else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
|
|
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
|
|
// Return the declared type to reduce follow-on errors
|
|
return type;
|
|
}
|
|
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
|
|
}
|
|
|
|
function isInsideFunction(node: Node, threshold: Node): boolean {
|
|
return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n));
|
|
}
|
|
|
|
function getPartOfForStatementContainingNode(node: Node, container: ForStatement) {
|
|
return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement);
|
|
}
|
|
|
|
function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void {
|
|
if (languageVersion >= ScriptTarget.ES2015 ||
|
|
(symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 ||
|
|
isSourceFile(symbol.valueDeclaration) ||
|
|
symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) {
|
|
return;
|
|
}
|
|
|
|
// 1. walk from the use site up to the declaration and check
|
|
// if there is anything function like between declaration and use-site (is binding/class is captured in function).
|
|
// 2. walk from the declaration up to the boundary of lexical environment and check
|
|
// if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement)
|
|
|
|
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
|
|
const usedInFunction = isInsideFunction(node.parent, container);
|
|
let current = container;
|
|
|
|
let containedInIterationStatement = false;
|
|
while (current && !nodeStartsNewLexicalEnvironment(current)) {
|
|
if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) {
|
|
containedInIterationStatement = true;
|
|
break;
|
|
}
|
|
current = current.parent;
|
|
}
|
|
|
|
if (containedInIterationStatement) {
|
|
if (usedInFunction) {
|
|
// mark iteration statement as containing block-scoped binding captured in some function
|
|
let capturesBlockScopeBindingInLoopBody = true;
|
|
if (isForStatement(container)) {
|
|
const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList);
|
|
if (varDeclList && varDeclList.parent === container) {
|
|
const part = getPartOfForStatementContainingNode(node.parent, container);
|
|
if (part) {
|
|
const links = getNodeLinks(part);
|
|
links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding;
|
|
|
|
const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []);
|
|
pushIfUnique(capturedBindings, symbol);
|
|
|
|
if (part === container.initializer) {
|
|
capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (capturesBlockScopeBindingInLoopBody) {
|
|
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
|
|
}
|
|
}
|
|
|
|
// mark variables that are declared in loop initializer and reassigned inside the body of ForStatement.
|
|
// if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back.
|
|
if (isForStatement(container)) {
|
|
const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList);
|
|
if (varDeclList && varDeclList.parent === container && isAssignedInBodyOfForStatement(node, container)) {
|
|
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter;
|
|
}
|
|
}
|
|
|
|
// set 'declared inside loop' bit on the block-scoped binding
|
|
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
|
|
}
|
|
|
|
if (usedInFunction) {
|
|
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding;
|
|
}
|
|
}
|
|
|
|
function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) {
|
|
const links = getNodeLinks(node);
|
|
return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl));
|
|
}
|
|
|
|
function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean {
|
|
// skip parenthesized nodes
|
|
let current: Node = node;
|
|
while (current.parent.kind === SyntaxKind.ParenthesizedExpression) {
|
|
current = current.parent;
|
|
}
|
|
|
|
// check if node is used as LHS in some assignment expression
|
|
let isAssigned = false;
|
|
if (isAssignmentTarget(current)) {
|
|
isAssigned = true;
|
|
}
|
|
else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) {
|
|
const expr = <PrefixUnaryExpression | PostfixUnaryExpression>current.parent;
|
|
isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken;
|
|
}
|
|
|
|
if (!isAssigned) {
|
|
return false;
|
|
}
|
|
|
|
// at this point we know that node is the target of assignment
|
|
// now check that modification happens inside the statement part of the ForStatement
|
|
return !!findAncestor(current, n => n === container ? "quit" : n === container.statement);
|
|
}
|
|
|
|
function captureLexicalThis(node: Node, container: Node): void {
|
|
getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis;
|
|
if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) {
|
|
const classNode = container.parent;
|
|
getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis;
|
|
}
|
|
else {
|
|
getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis;
|
|
}
|
|
}
|
|
|
|
function findFirstSuperCall(n: Node): SuperCall | undefined {
|
|
if (isSuperCall(n)) {
|
|
return n;
|
|
}
|
|
else if (isFunctionLike(n)) {
|
|
return undefined;
|
|
}
|
|
return forEachChild(n, findFirstSuperCall);
|
|
}
|
|
|
|
/**
|
|
* Return a cached result if super-statement is already found.
|
|
* Otherwise, find a super statement in a given constructor function and cache the result in the node-links of the constructor
|
|
*
|
|
* @param constructor constructor-function to look for super statement
|
|
*/
|
|
function getSuperCallInConstructor(constructor: ConstructorDeclaration): SuperCall | undefined {
|
|
const links = getNodeLinks(constructor);
|
|
|
|
// Only trying to find super-call if we haven't yet tried to find one. Once we try, we will record the result
|
|
if (links.hasSuperCall === undefined) {
|
|
links.superCall = findFirstSuperCall(constructor.body!);
|
|
links.hasSuperCall = links.superCall ? true : false;
|
|
}
|
|
return links.superCall!;
|
|
}
|
|
|
|
/**
|
|
* Check if the given class-declaration extends null then return true.
|
|
* Otherwise, return false
|
|
* @param classDecl a class declaration to check if it extends null
|
|
*/
|
|
function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean {
|
|
const classSymbol = getSymbolOfNode(classDecl);
|
|
const classInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(classSymbol);
|
|
const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType);
|
|
|
|
return baseConstructorType === nullWideningType;
|
|
}
|
|
|
|
function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) {
|
|
const containingClassDecl = <ClassDeclaration>container.parent;
|
|
const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl);
|
|
|
|
// If a containing class does not have extends clause or the class extends null
|
|
// skip checking whether super statement is called before "this" accessing.
|
|
if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) {
|
|
const superCall = getSuperCallInConstructor(<ConstructorDeclaration>container);
|
|
|
|
// We should give an error in the following cases:
|
|
// - No super-call
|
|
// - "this" is accessing before super-call.
|
|
// i.e super(this)
|
|
// this.x; super();
|
|
// We want to make sure that super-call is done before accessing "this" so that
|
|
// "this" is not accessed as a parameter of the super-call.
|
|
if (!superCall || superCall.end > node.pos) {
|
|
// In ES6, super inside constructor of class-declaration has to precede "this" accessing
|
|
error(node, diagnosticMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkThisExpression(node: Node): Type {
|
|
// Stop at the first arrow function so that we can
|
|
// tell whether 'this' needs to be captured.
|
|
let container = getThisContainer(node, /* includeArrowFunctions */ true);
|
|
let capturedByArrowFunction = false;
|
|
|
|
if (container.kind === SyntaxKind.Constructor) {
|
|
checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class);
|
|
}
|
|
|
|
// Now skip arrow functions to get the "real" owner of 'this'.
|
|
if (container.kind === SyntaxKind.ArrowFunction) {
|
|
container = getThisContainer(container, /* includeArrowFunctions */ false);
|
|
capturedByArrowFunction = true;
|
|
}
|
|
|
|
switch (container.kind) {
|
|
case SyntaxKind.ModuleDeclaration:
|
|
error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body);
|
|
// do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
|
|
break;
|
|
case SyntaxKind.EnumDeclaration:
|
|
error(node, Diagnostics.this_cannot_be_referenced_in_current_location);
|
|
// do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
|
|
break;
|
|
case SyntaxKind.Constructor:
|
|
if (isInConstructorArgumentInitializer(node, container)) {
|
|
error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments);
|
|
// do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
|
|
}
|
|
break;
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
if (hasModifier(container, ModifierFlags.Static) && !(compilerOptions.target === ScriptTarget.ESNext && compilerOptions.useDefineForClassFields)) {
|
|
error(node, Diagnostics.this_cannot_be_referenced_in_a_static_property_initializer);
|
|
// do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
|
|
}
|
|
break;
|
|
case SyntaxKind.ComputedPropertyName:
|
|
error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name);
|
|
break;
|
|
}
|
|
|
|
// When targeting es6, mark that we'll need to capture `this` in its lexically bound scope.
|
|
if (capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) {
|
|
captureLexicalThis(node, container);
|
|
}
|
|
|
|
const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container);
|
|
if (noImplicitThis) {
|
|
const globalThisType = getTypeOfSymbol(globalThisSymbol);
|
|
if (type === globalThisType && capturedByArrowFunction) {
|
|
error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this);
|
|
}
|
|
else if (!type) {
|
|
// With noImplicitThis, functions may not reference 'this' if it has type 'any'
|
|
const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation);
|
|
if (!isSourceFile(container)) {
|
|
const outsideThis = tryGetThisTypeAt(container);
|
|
if (outsideThis && outsideThis !== globalThisType) {
|
|
addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return type || anyType;
|
|
}
|
|
|
|
function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined {
|
|
const isInJS = isInJSFile(node);
|
|
if (isFunctionLike(container) &&
|
|
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
|
|
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
|
|
// If this is a function in a JS file, it might be a class method.
|
|
const className = getClassNameFromPrototypeMethod(container);
|
|
if (isInJS && className) {
|
|
const classSymbol = checkExpression(className).symbol;
|
|
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
|
|
const classType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType;
|
|
if (classType) {
|
|
return getFlowTypeOfReference(node, classType);
|
|
}
|
|
}
|
|
}
|
|
// Check if it's a constructor definition, can be either a variable decl or function decl
|
|
// i.e.
|
|
// * /** @constructor */ function [name]() { ... }
|
|
// * /** @constructor */ var x = function() { ... }
|
|
else if (isInJS &&
|
|
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
|
|
getJSDocClassTag(container)) {
|
|
const classType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType!;
|
|
return getFlowTypeOfReference(node, classType);
|
|
}
|
|
|
|
const thisType = getThisTypeOfDeclaration(container) || getContextualThisParameterType(container);
|
|
if (thisType) {
|
|
return getFlowTypeOfReference(node, thisType);
|
|
}
|
|
}
|
|
|
|
if (isClassLike(container.parent)) {
|
|
const symbol = getSymbolOfNode(container.parent);
|
|
const type = hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!;
|
|
return getFlowTypeOfReference(node, type);
|
|
}
|
|
|
|
if (isInJS) {
|
|
const type = getTypeForThisExpressionFromJSDoc(container);
|
|
if (type && type !== errorType) {
|
|
return getFlowTypeOfReference(node, type);
|
|
}
|
|
}
|
|
if (isSourceFile(container)) {
|
|
// look up in the source file's locals or exports
|
|
if (container.commonJsModuleIndicator) {
|
|
const fileSymbol = getSymbolOfNode(container);
|
|
return fileSymbol && getTypeOfSymbol(fileSymbol);
|
|
}
|
|
else if (includeGlobalThis) {
|
|
return getTypeOfSymbol(globalThisSymbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getExplicitThisType(node: Expression) {
|
|
const container = getThisContainer(node, /*includeArrowFunctions*/ false);
|
|
if (isFunctionLike(container)) {
|
|
const signature = getSignatureFromDeclaration(container);
|
|
if (signature.thisParameter) {
|
|
return getExplicitTypeOfSymbol(signature.thisParameter);
|
|
}
|
|
}
|
|
if (isClassLike(container.parent)) {
|
|
const symbol = getSymbolOfNode(container.parent);
|
|
return hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!;
|
|
}
|
|
}
|
|
|
|
function getClassNameFromPrototypeMethod(container: Node) {
|
|
// Check if it's the RHS of a x.prototype.y = function [name]() { .... }
|
|
if (container.kind === SyntaxKind.FunctionExpression &&
|
|
isBinaryExpression(container.parent) &&
|
|
getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) {
|
|
// Get the 'x' of 'x.prototype.y = container'
|
|
return ((container.parent // x.prototype.y = container
|
|
.left as PropertyAccessExpression) // x.prototype.y
|
|
.expression as PropertyAccessExpression) // x.prototype
|
|
.expression; // x
|
|
}
|
|
// x.prototype = { method() { } }
|
|
else if (container.kind === SyntaxKind.MethodDeclaration &&
|
|
container.parent.kind === SyntaxKind.ObjectLiteralExpression &&
|
|
isBinaryExpression(container.parent.parent) &&
|
|
getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) {
|
|
return (container.parent.parent.left as PropertyAccessExpression).expression;
|
|
}
|
|
// x.prototype = { method: function() { } }
|
|
else if (container.kind === SyntaxKind.FunctionExpression &&
|
|
container.parent.kind === SyntaxKind.PropertyAssignment &&
|
|
container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression &&
|
|
isBinaryExpression(container.parent.parent.parent) &&
|
|
getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) {
|
|
return (container.parent.parent.parent.left as PropertyAccessExpression).expression;
|
|
}
|
|
// Object.defineProperty(x, "method", { value: function() { } });
|
|
// Object.defineProperty(x, "method", { set: (x: () => void) => void });
|
|
// Object.defineProperty(x, "method", { get: () => function() { }) });
|
|
else if (container.kind === SyntaxKind.FunctionExpression &&
|
|
isPropertyAssignment(container.parent) &&
|
|
isIdentifier(container.parent.name) &&
|
|
(container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") &&
|
|
isObjectLiteralExpression(container.parent.parent) &&
|
|
isCallExpression(container.parent.parent.parent) &&
|
|
container.parent.parent.parent.arguments[2] === container.parent.parent &&
|
|
getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) {
|
|
return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression;
|
|
}
|
|
// Object.defineProperty(x, "method", { value() { } });
|
|
// Object.defineProperty(x, "method", { set(x: () => void) {} });
|
|
// Object.defineProperty(x, "method", { get() { return () => {} } });
|
|
else if (isMethodDeclaration(container) &&
|
|
isIdentifier(container.name) &&
|
|
(container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") &&
|
|
isObjectLiteralExpression(container.parent) &&
|
|
isCallExpression(container.parent.parent) &&
|
|
container.parent.parent.arguments[2] === container.parent &&
|
|
getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) {
|
|
return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression;
|
|
}
|
|
}
|
|
|
|
function getTypeForThisExpressionFromJSDoc(node: Node) {
|
|
const jsdocType = getJSDocType(node);
|
|
if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) {
|
|
const jsDocFunctionType = <JSDocFunctionType>jsdocType;
|
|
if (jsDocFunctionType.parameters.length > 0 &&
|
|
jsDocFunctionType.parameters[0].name &&
|
|
(jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) {
|
|
return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!);
|
|
}
|
|
}
|
|
const thisTag = getJSDocThisTag(node);
|
|
if (thisTag && thisTag.typeExpression) {
|
|
return getTypeFromTypeNode(thisTag.typeExpression);
|
|
}
|
|
}
|
|
|
|
function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean {
|
|
return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl);
|
|
}
|
|
|
|
function checkSuperExpression(node: Node): Type {
|
|
const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).expression === node;
|
|
|
|
let container = getSuperContainer(node, /*stopOnFunctions*/ true);
|
|
let needToCaptureLexicalThis = false;
|
|
|
|
// adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting
|
|
if (!isCallExpression) {
|
|
while (container && container.kind === SyntaxKind.ArrowFunction) {
|
|
container = getSuperContainer(container, /*stopOnFunctions*/ true);
|
|
needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015;
|
|
}
|
|
}
|
|
|
|
const canUseSuperExpression = isLegalUsageOfSuperExpression(container);
|
|
let nodeCheckFlag: NodeCheckFlags = 0;
|
|
|
|
if (!canUseSuperExpression) {
|
|
// issue more specific error if super is used in computed property name
|
|
// class A { foo() { return "1" }}
|
|
// class B {
|
|
// [super.foo()]() {}
|
|
// }
|
|
const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName);
|
|
if (current && current.kind === SyntaxKind.ComputedPropertyName) {
|
|
error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name);
|
|
}
|
|
else if (isCallExpression) {
|
|
error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors);
|
|
}
|
|
else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) {
|
|
error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions);
|
|
}
|
|
else {
|
|
error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class);
|
|
}
|
|
return errorType;
|
|
}
|
|
|
|
if (!isCallExpression && container.kind === SyntaxKind.Constructor) {
|
|
checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class);
|
|
}
|
|
|
|
if (hasModifier(container, ModifierFlags.Static) || isCallExpression) {
|
|
nodeCheckFlag = NodeCheckFlags.SuperStatic;
|
|
}
|
|
else {
|
|
nodeCheckFlag = NodeCheckFlags.SuperInstance;
|
|
}
|
|
|
|
getNodeLinks(node).flags |= nodeCheckFlag;
|
|
|
|
// Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference.
|
|
// This is due to the fact that we emit the body of an async function inside of a generator function. As generator
|
|
// functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper
|
|
// uses an arrow function, which is permitted to reference `super`.
|
|
//
|
|
// There are two primary ways we can access `super` from within an async method. The first is getting the value of a property
|
|
// or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value
|
|
// of a property or indexed access, either as part of an assignment expression or destructuring assignment.
|
|
//
|
|
// The simplest case is reading a value, in which case we will emit something like the following:
|
|
//
|
|
// // ts
|
|
// ...
|
|
// async asyncMethod() {
|
|
// let x = await super.asyncMethod();
|
|
// return x;
|
|
// }
|
|
// ...
|
|
//
|
|
// // js
|
|
// ...
|
|
// asyncMethod() {
|
|
// const _super = Object.create(null, {
|
|
// asyncMethod: { get: () => super.asyncMethod },
|
|
// });
|
|
// return __awaiter(this, arguments, Promise, function *() {
|
|
// let x = yield _super.asyncMethod.call(this);
|
|
// return x;
|
|
// });
|
|
// }
|
|
// ...
|
|
//
|
|
// The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases
|
|
// are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment:
|
|
//
|
|
// // ts
|
|
// ...
|
|
// async asyncMethod(ar: Promise<any[]>) {
|
|
// [super.a, super.b] = await ar;
|
|
// }
|
|
// ...
|
|
//
|
|
// // js
|
|
// ...
|
|
// asyncMethod(ar) {
|
|
// const _super = Object.create(null, {
|
|
// a: { get: () => super.a, set: (v) => super.a = v },
|
|
// b: { get: () => super.b, set: (v) => super.b = v }
|
|
// };
|
|
// return __awaiter(this, arguments, Promise, function *() {
|
|
// [_super.a, _super.b] = yield ar;
|
|
// });
|
|
// }
|
|
// ...
|
|
//
|
|
// Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments
|
|
// as a call expression cannot be used as the target of a destructuring assignment while a property access can.
|
|
//
|
|
// For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations.
|
|
if (container.kind === SyntaxKind.MethodDeclaration && hasModifier(container, ModifierFlags.Async)) {
|
|
if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) {
|
|
getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding;
|
|
}
|
|
else {
|
|
getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper;
|
|
}
|
|
}
|
|
|
|
if (needToCaptureLexicalThis) {
|
|
// call expressions are allowed only in constructors so they should always capture correct 'this'
|
|
// super property access expressions can also appear in arrow functions -
|
|
// in this case they should also use correct lexical this
|
|
captureLexicalThis(node.parent, container);
|
|
}
|
|
|
|
if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
if (languageVersion < ScriptTarget.ES2015) {
|
|
error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher);
|
|
return errorType;
|
|
}
|
|
else {
|
|
// for object literal assume that type of 'super' is 'any'
|
|
return anyType;
|
|
}
|
|
}
|
|
|
|
// at this point the only legal case for parent is ClassLikeDeclaration
|
|
const classLikeDeclaration = <ClassLikeDeclaration>container.parent;
|
|
if (!getClassExtendsHeritageElement(classLikeDeclaration)) {
|
|
error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class);
|
|
return errorType;
|
|
}
|
|
|
|
const classType = <InterfaceType>getDeclaredTypeOfSymbol(getSymbolOfNode(classLikeDeclaration));
|
|
const baseClassType = classType && getBaseTypes(classType)[0];
|
|
if (!baseClassType) {
|
|
return errorType;
|
|
}
|
|
|
|
if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) {
|
|
// issue custom error message for super property access in constructor arguments (to be aligned with old compiler)
|
|
error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments);
|
|
return errorType;
|
|
}
|
|
|
|
return nodeCheckFlag === NodeCheckFlags.SuperStatic
|
|
? getBaseConstructorTypeOfClass(classType)
|
|
: getTypeWithThisArgument(baseClassType, classType.thisType);
|
|
|
|
function isLegalUsageOfSuperExpression(container: Node): boolean {
|
|
if (!container) {
|
|
return false;
|
|
}
|
|
|
|
if (isCallExpression) {
|
|
// TS 1.0 SPEC (April 2014): 4.8.1
|
|
// Super calls are only permitted in constructors of derived classes
|
|
return container.kind === SyntaxKind.Constructor;
|
|
}
|
|
else {
|
|
// TS 1.0 SPEC (April 2014)
|
|
// 'super' property access is allowed
|
|
// - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance
|
|
// - In a static member function or static member accessor
|
|
|
|
// topmost container must be something that is directly nested in the class declaration\object literal expression
|
|
if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
if (hasModifier(container, ModifierFlags.Static)) {
|
|
return container.kind === SyntaxKind.MethodDeclaration ||
|
|
container.kind === SyntaxKind.MethodSignature ||
|
|
container.kind === SyntaxKind.GetAccessor ||
|
|
container.kind === SyntaxKind.SetAccessor;
|
|
}
|
|
else {
|
|
return container.kind === SyntaxKind.MethodDeclaration ||
|
|
container.kind === SyntaxKind.MethodSignature ||
|
|
container.kind === SyntaxKind.GetAccessor ||
|
|
container.kind === SyntaxKind.SetAccessor ||
|
|
container.kind === SyntaxKind.PropertyDeclaration ||
|
|
container.kind === SyntaxKind.PropertySignature ||
|
|
container.kind === SyntaxKind.Constructor;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined {
|
|
return (func.kind === SyntaxKind.MethodDeclaration ||
|
|
func.kind === SyntaxKind.GetAccessor ||
|
|
func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent :
|
|
func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? <ObjectLiteralExpression>func.parent.parent :
|
|
undefined;
|
|
}
|
|
|
|
function getThisTypeArgument(type: Type): Type | undefined {
|
|
return getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).target === globalThisType ? getTypeArguments(<TypeReference>type)[0] : undefined;
|
|
}
|
|
|
|
function getThisTypeFromContextualType(type: Type): Type | undefined {
|
|
return mapType(type, t => {
|
|
return t.flags & TypeFlags.Intersection ? forEach((<IntersectionType>t).types, getThisTypeArgument) : getThisTypeArgument(t);
|
|
});
|
|
}
|
|
|
|
function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined {
|
|
if (func.kind === SyntaxKind.ArrowFunction) {
|
|
return undefined;
|
|
}
|
|
if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
|
|
const contextualSignature = getContextualSignature(func);
|
|
if (contextualSignature) {
|
|
const thisParameter = contextualSignature.thisParameter;
|
|
if (thisParameter) {
|
|
return getTypeOfSymbol(thisParameter);
|
|
}
|
|
}
|
|
}
|
|
const inJs = isInJSFile(func);
|
|
if (noImplicitThis || inJs) {
|
|
const containingLiteral = getContainingObjectLiteral(func);
|
|
if (containingLiteral) {
|
|
// We have an object literal method. Check if the containing object literal has a contextual type
|
|
// that includes a ThisType<T>. If so, T is the contextual type for 'this'. We continue looking in
|
|
// any directly enclosing object literals.
|
|
const contextualType = getApparentTypeOfContextualType(containingLiteral);
|
|
let literal = containingLiteral;
|
|
let type = contextualType;
|
|
while (type) {
|
|
const thisType = getThisTypeFromContextualType(type);
|
|
if (thisType) {
|
|
return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral)));
|
|
}
|
|
if (literal.parent.kind !== SyntaxKind.PropertyAssignment) {
|
|
break;
|
|
}
|
|
literal = <ObjectLiteralExpression>literal.parent.parent;
|
|
type = getApparentTypeOfContextualType(literal);
|
|
}
|
|
// There was no contextual ThisType<T> for the containing object literal, so the contextual type
|
|
// for 'this' is the non-null form of the contextual type for the containing object literal or
|
|
// the type of the object literal itself.
|
|
return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral));
|
|
}
|
|
// In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the
|
|
// contextual type for 'this' is 'obj'.
|
|
const parent = walkUpParenthesizedExpressions(func.parent);
|
|
if (parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>parent).operatorToken.kind === SyntaxKind.EqualsToken) {
|
|
const target = (<BinaryExpression>parent).left;
|
|
if (isAccessExpression(target)) {
|
|
const { expression } = target;
|
|
// Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }`
|
|
if (inJs && isIdentifier(expression)) {
|
|
const sourceFile = getSourceFileOfNode(parent);
|
|
if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
return getWidenedType(checkExpressionCached(expression));
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Return contextual type of parameter or undefined if no contextual type is available
|
|
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined {
|
|
const func = parameter.parent;
|
|
if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
|
|
return undefined;
|
|
}
|
|
const iife = getImmediatelyInvokedFunctionExpression(func);
|
|
if (iife && iife.arguments) {
|
|
const args = getEffectiveCallArguments(iife);
|
|
const indexOfParameter = func.parameters.indexOf(parameter);
|
|
if (parameter.dotDotDotToken) {
|
|
return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined);
|
|
}
|
|
const links = getNodeLinks(iife);
|
|
const cached = links.resolvedSignature;
|
|
links.resolvedSignature = anySignature;
|
|
const type = indexOfParameter < args.length ?
|
|
getWidenedLiteralType(checkExpression(args[indexOfParameter])) :
|
|
parameter.initializer ? undefined : undefinedWideningType;
|
|
links.resolvedSignature = cached;
|
|
return type;
|
|
}
|
|
const contextualSignature = getContextualSignature(func);
|
|
if (contextualSignature) {
|
|
const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0);
|
|
return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ?
|
|
getRestTypeAtPosition(contextualSignature, index) :
|
|
tryGetTypeAtPosition(contextualSignature, index);
|
|
}
|
|
}
|
|
|
|
function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type | undefined {
|
|
const typeNode = getEffectiveTypeAnnotationNode(declaration);
|
|
if (typeNode) {
|
|
return getTypeFromTypeNode(typeNode);
|
|
}
|
|
switch (declaration.kind) {
|
|
case SyntaxKind.Parameter:
|
|
return getContextuallyTypedParameterType(declaration);
|
|
case SyntaxKind.BindingElement:
|
|
return getContextualTypeForBindingElement(declaration);
|
|
// By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent
|
|
}
|
|
}
|
|
|
|
function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined {
|
|
const parent = declaration.parent.parent;
|
|
const name = declaration.propertyName || declaration.name;
|
|
const parentType = getContextualTypeForVariableLikeDeclaration(parent) ||
|
|
parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent);
|
|
if (parentType && !isBindingPattern(name) && !isComputedNonLiteralName(name)) {
|
|
const nameType = getLiteralTypeFromPropertyName(name);
|
|
if (isTypeUsableAsPropertyName(nameType)) {
|
|
const text = getPropertyNameFromType(nameType);
|
|
return getTypeOfPropertyOfType(parentType, text);
|
|
}
|
|
}
|
|
}
|
|
|
|
// In a variable, parameter or property declaration with a type annotation,
|
|
// the contextual type of an initializer expression is the type of the variable, parameter or property.
|
|
// Otherwise, in a parameter declaration of a contextually typed function expression,
|
|
// the contextual type of an initializer expression is the contextual type of the parameter.
|
|
// Otherwise, in a variable or parameter declaration with a binding pattern name,
|
|
// the contextual type of an initializer expression is the type implied by the binding pattern.
|
|
// Otherwise, in a binding pattern inside a variable or parameter declaration,
|
|
// the contextual type of an initializer expression is the type annotation of the containing declaration, if present.
|
|
function getContextualTypeForInitializerExpression(node: Expression): Type | undefined {
|
|
const declaration = <VariableLikeDeclaration>node.parent;
|
|
if (hasInitializer(declaration) && node === declaration.initializer) {
|
|
const result = getContextualTypeForVariableLikeDeclaration(declaration);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
if (isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable
|
|
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false);
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getContextualTypeForReturnExpression(node: Expression): Type | undefined {
|
|
const func = getContainingFunction(node);
|
|
if (func) {
|
|
const functionFlags = getFunctionFlags(func);
|
|
if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function
|
|
return undefined;
|
|
}
|
|
|
|
const contextualReturnType = getContextualReturnType(func);
|
|
if (contextualReturnType) {
|
|
if (functionFlags & FunctionFlags.Async) { // Async function
|
|
const contextualAwaitedType = getAwaitedTypeOfPromise(contextualReturnType);
|
|
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
|
|
}
|
|
return contextualReturnType; // Regular function
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getContextualTypeForAwaitOperand(node: AwaitExpression): Type | undefined {
|
|
const contextualType = getContextualType(node);
|
|
if (contextualType) {
|
|
const contextualAwaitedType = getAwaitedType(contextualType);
|
|
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getContextualTypeForYieldOperand(node: YieldExpression): Type | undefined {
|
|
const func = getContainingFunction(node);
|
|
if (func) {
|
|
const functionFlags = getFunctionFlags(func);
|
|
const contextualReturnType = getContextualReturnType(func);
|
|
if (contextualReturnType) {
|
|
return node.asteriskToken
|
|
? contextualReturnType
|
|
: getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0);
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function isInParameterInitializerBeforeContainingFunction(node: Node) {
|
|
let inBindingInitializer = false;
|
|
while (node.parent && !isFunctionLike(node.parent)) {
|
|
if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) {
|
|
return true;
|
|
}
|
|
if (isBindingElement(node.parent) && node.parent.initializer === node) {
|
|
inBindingInitializer = true;
|
|
}
|
|
|
|
node = node.parent;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): Type | undefined {
|
|
const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async);
|
|
const contextualReturnType = getContextualReturnType(functionDecl);
|
|
if (contextualReturnType) {
|
|
return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync)
|
|
|| undefined;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function getContextualReturnType(functionDecl: SignatureDeclaration): Type | undefined {
|
|
// If the containing function has a return type annotation, is a constructor, or is a get accessor whose
|
|
// corresponding set accessor has a type annotation, return statements in the function are contextually typed
|
|
const returnType = getReturnTypeFromAnnotation(functionDecl);
|
|
if (returnType) {
|
|
return returnType;
|
|
}
|
|
// Otherwise, if the containing function is contextually typed by a function type with exactly one call signature
|
|
// and that call signature is non-generic, return statements are contextually typed by the return type of the signature
|
|
const signature = getContextualSignatureForFunctionLikeDeclaration(<FunctionExpression>functionDecl);
|
|
if (signature && !isResolvingReturnTypeOfSignature(signature)) {
|
|
return getReturnTypeOfSignature(signature);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter.
|
|
function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined {
|
|
const args = getEffectiveCallArguments(callTarget);
|
|
const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression
|
|
return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex);
|
|
}
|
|
|
|
function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type {
|
|
// If we're already in the process of resolving the given signature, don't resolve again as
|
|
// that could cause infinite recursion. Instead, return anySignature.
|
|
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
|
|
|
|
if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) {
|
|
return getEffectiveFirstArgumentForJsxSignature(signature, callTarget);
|
|
}
|
|
return getTypeAtPosition(signature, argIndex);
|
|
}
|
|
|
|
function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) {
|
|
if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) {
|
|
return getContextualTypeForArgument(<TaggedTemplateExpression>template.parent, substitutionExpression);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function getContextualTypeForBinaryOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined {
|
|
const binaryExpression = <BinaryExpression>node.parent;
|
|
const { left, operatorToken, right } = binaryExpression;
|
|
switch (operatorToken.kind) {
|
|
case SyntaxKind.EqualsToken:
|
|
if (node !== right) {
|
|
return undefined;
|
|
}
|
|
const contextSensitive = getIsContextSensitiveAssignmentOrContextType(binaryExpression);
|
|
if (!contextSensitive) {
|
|
return undefined;
|
|
}
|
|
return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive;
|
|
case SyntaxKind.BarBarToken:
|
|
case SyntaxKind.QuestionQuestionToken:
|
|
// When an || expression has a contextual type, the operands are contextually typed by that type, except
|
|
// when that type originates in a binding pattern, the right operand is contextually typed by the type of
|
|
// the left operand. When an || expression has no contextual type, the right operand is contextually typed
|
|
// by the type of the left operand, except for the special case of Javascript declarations of the form
|
|
// `namespace.prop = namespace.prop || {}`.
|
|
const type = getContextualType(binaryExpression, contextFlags);
|
|
return node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ?
|
|
getTypeOfExpression(left) : type;
|
|
case SyntaxKind.AmpersandAmpersandToken:
|
|
case SyntaxKind.CommaToken:
|
|
return node === right ? getContextualType(binaryExpression, contextFlags) : undefined;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
// In an assignment expression, the right operand is contextually typed by the type of the left operand.
|
|
// Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
|
|
function getIsContextSensitiveAssignmentOrContextType(binaryExpression: BinaryExpression): boolean | Type {
|
|
const kind = getAssignmentDeclarationKind(binaryExpression);
|
|
switch (kind) {
|
|
case AssignmentDeclarationKind.None:
|
|
return true;
|
|
case AssignmentDeclarationKind.Property:
|
|
case AssignmentDeclarationKind.ExportsProperty:
|
|
case AssignmentDeclarationKind.Prototype:
|
|
case AssignmentDeclarationKind.PrototypeProperty:
|
|
// If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration.
|
|
// See `bindStaticPropertyAssignment` in `binder.ts`.
|
|
if (!binaryExpression.left.symbol) {
|
|
return true;
|
|
}
|
|
else {
|
|
const decl = binaryExpression.left.symbol.valueDeclaration;
|
|
if (!decl) {
|
|
return false;
|
|
}
|
|
const lhs = cast(binaryExpression.left, isAccessExpression);
|
|
const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
|
|
if (overallAnnotation) {
|
|
return getTypeFromTypeNode(overallAnnotation);
|
|
}
|
|
else if (isIdentifier(lhs.expression)) {
|
|
const id = lhs.expression;
|
|
const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true);
|
|
if (parentSymbol) {
|
|
const annotated = getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration);
|
|
if (annotated) {
|
|
const nameStr = getElementOrPropertyAccessName(lhs);
|
|
if (nameStr !== undefined) {
|
|
const type = getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr);
|
|
return type || false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return !isInJSFile(decl);
|
|
}
|
|
case AssignmentDeclarationKind.ModuleExports:
|
|
case AssignmentDeclarationKind.ThisProperty:
|
|
if (!binaryExpression.symbol) return true;
|
|
if (binaryExpression.symbol.valueDeclaration) {
|
|
const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration);
|
|
if (annotated) {
|
|
const type = getTypeFromTypeNode(annotated);
|
|
if (type) {
|
|
return type;
|
|
}
|
|
}
|
|
}
|
|
if (kind === AssignmentDeclarationKind.ModuleExports) return false;
|
|
const thisAccess = cast(binaryExpression.left, isAccessExpression);
|
|
if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) {
|
|
return false;
|
|
}
|
|
const thisType = checkThisExpression(thisAccess.expression);
|
|
const nameStr = getElementOrPropertyAccessName(thisAccess);
|
|
return nameStr !== undefined && thisType && getTypeOfPropertyOfContextualType(thisType, nameStr) || false;
|
|
case AssignmentDeclarationKind.ObjectDefinePropertyValue:
|
|
case AssignmentDeclarationKind.ObjectDefinePropertyExports:
|
|
case AssignmentDeclarationKind.ObjectDefinePrototypeProperty:
|
|
return Debug.fail("Does not apply");
|
|
default:
|
|
return Debug.assertNever(kind);
|
|
}
|
|
}
|
|
|
|
function getTypeOfPropertyOfContextualType(type: Type, name: __String) {
|
|
return mapType(type, t => {
|
|
if (isGenericMappedType(t)) {
|
|
const constraint = getConstraintTypeFromMappedType(t);
|
|
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
|
|
const propertyNameType = getLiteralType(unescapeLeadingUnderscores(name));
|
|
if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
|
|
return substituteIndexedMappedType(t, propertyNameType);
|
|
}
|
|
}
|
|
else if (t.flags & TypeFlags.StructuredType) {
|
|
const prop = getPropertyOfType(t, name);
|
|
if (prop) {
|
|
return getTypeOfSymbol(prop);
|
|
}
|
|
if (isTupleType(t)) {
|
|
const restType = getRestTypeOfTupleType(t);
|
|
if (restType && isNumericLiteralName(name) && +name >= 0) {
|
|
return restType;
|
|
}
|
|
}
|
|
return isNumericLiteralName(name) && getIndexTypeOfContextualType(t, IndexKind.Number) ||
|
|
getIndexTypeOfContextualType(t, IndexKind.String);
|
|
}
|
|
return undefined;
|
|
}, /*noReductions*/ true);
|
|
}
|
|
|
|
function getIndexTypeOfContextualType(type: Type, kind: IndexKind) {
|
|
return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true);
|
|
}
|
|
|
|
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
|
|
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
|
|
// exists. Otherwise, it is the type of the string index signature in T, if one exists.
|
|
function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags?: ContextFlags): Type | undefined {
|
|
Debug.assert(isObjectLiteralMethod(node));
|
|
if (node.flags & NodeFlags.InWithStatement) {
|
|
// We cannot answer semantic questions within a with block, do not proceed any further
|
|
return undefined;
|
|
}
|
|
return getContextualTypeForObjectLiteralElement(node, contextFlags);
|
|
}
|
|
|
|
function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) {
|
|
const objectLiteral = <ObjectLiteralExpression>element.parent;
|
|
const type = getApparentTypeOfContextualType(objectLiteral, contextFlags);
|
|
if (type) {
|
|
if (!hasNonBindableDynamicName(element)) {
|
|
// For a (non-symbol) computed property, there is no reason to look up the name
|
|
// in the type. It will just be "__computed", which does not appear in any
|
|
// SymbolTable.
|
|
const symbolName = getSymbolOfNode(element).escapedName;
|
|
const propertyType = getTypeOfPropertyOfContextualType(type, symbolName);
|
|
if (propertyType) {
|
|
return propertyType;
|
|
}
|
|
}
|
|
return isNumericName(element.name!) && getIndexTypeOfContextualType(type, IndexKind.Number) ||
|
|
getIndexTypeOfContextualType(type, IndexKind.String);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// In an array literal contextually typed by a type T, the contextual type of an element expression at index N is
|
|
// the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature,
|
|
// it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated
|
|
// type of T.
|
|
function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined {
|
|
return arrayContextualType && (
|
|
getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String)
|
|
|| getIteratedTypeOrElementType(IterationUse.Element, arrayContextualType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false));
|
|
}
|
|
|
|
// In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type.
|
|
function getContextualTypeForConditionalOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined {
|
|
const conditional = <ConditionalExpression>node.parent;
|
|
return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined;
|
|
}
|
|
|
|
function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) {
|
|
const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName);
|
|
// JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty)
|
|
const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
|
|
if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) {
|
|
return undefined;
|
|
}
|
|
const realChildren = getSemanticJsxChildren(node.children);
|
|
const childIndex = realChildren.indexOf(child);
|
|
const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName);
|
|
return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => {
|
|
if (isArrayLikeType(t)) {
|
|
return getIndexedAccessType(t, getLiteralType(childIndex));
|
|
}
|
|
else {
|
|
return t;
|
|
}
|
|
}, /*noReductions*/ true));
|
|
}
|
|
|
|
function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined {
|
|
const exprParent = node.parent;
|
|
return isJsxAttributeLike(exprParent)
|
|
? getContextualType(node)
|
|
: isJsxElement(exprParent)
|
|
? getContextualTypeForChildJsxExpression(exprParent, node)
|
|
: undefined;
|
|
}
|
|
|
|
function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined {
|
|
// When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
|
|
// which is a type of the parameter of the signature we are trying out.
|
|
// If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName
|
|
if (isJsxAttribute(attribute)) {
|
|
const attributesType = getApparentTypeOfContextualType(attribute.parent);
|
|
if (!attributesType || isTypeAny(attributesType)) {
|
|
return undefined;
|
|
}
|
|
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
|
|
}
|
|
else {
|
|
return getContextualType(attribute.parent);
|
|
}
|
|
}
|
|
|
|
// Return true if the given expression is possibly a discriminant value. We limit the kinds of
|
|
// expressions we check to those that don't depend on their contextual type in order not to cause
|
|
// recursive (and possibly infinite) invocations of getContextualType.
|
|
function isPossiblyDiscriminantValue(node: Expression): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.BigIntLiteral:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
case SyntaxKind.TrueKeyword:
|
|
case SyntaxKind.FalseKeyword:
|
|
case SyntaxKind.NullKeyword:
|
|
case SyntaxKind.Identifier:
|
|
case SyntaxKind.UndefinedKeyword:
|
|
return true;
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return isPossiblyDiscriminantValue((<PropertyAccessExpression | ParenthesizedExpression>node).expression);
|
|
case SyntaxKind.JsxExpression:
|
|
return !(node as JsxExpression).expression || isPossiblyDiscriminantValue((node as JsxExpression).expression!);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) {
|
|
return discriminateTypeByDiscriminableItems(contextualType,
|
|
map(
|
|
filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)),
|
|
prop => ([() => checkExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String])
|
|
),
|
|
isTypeAssignableTo,
|
|
contextualType
|
|
);
|
|
}
|
|
|
|
function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) {
|
|
return discriminateTypeByDiscriminableItems(contextualType,
|
|
map(
|
|
filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))),
|
|
prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => checkExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String])
|
|
),
|
|
isTypeAssignableTo,
|
|
contextualType
|
|
);
|
|
}
|
|
|
|
// Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily
|
|
// be "pushed" onto a node using the contextualType property.
|
|
function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags?: ContextFlags): Type | undefined {
|
|
const contextualType = isObjectLiteralMethod(node) ?
|
|
getContextualTypeForObjectLiteralMethod(node, contextFlags) :
|
|
getContextualType(node, contextFlags);
|
|
const instantiatedType = instantiateContextualType(contextualType, node, contextFlags);
|
|
if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) {
|
|
const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true);
|
|
if (apparentType.flags & TypeFlags.Union) {
|
|
if (isObjectLiteralExpression(node)) {
|
|
return discriminateContextualTypeByObjectMembers(node, apparentType as UnionType);
|
|
}
|
|
else if (isJsxAttributes(node)) {
|
|
return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType);
|
|
}
|
|
}
|
|
return apparentType;
|
|
}
|
|
}
|
|
|
|
// If the given contextual type contains instantiable types and if a mapper representing
|
|
// return type inferences is available, instantiate those types using that mapper.
|
|
function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags?: ContextFlags): Type | undefined {
|
|
if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) {
|
|
const inferenceContext = getInferenceContext(node);
|
|
// If no inferences have been made, nothing is gained from instantiating as type parameters
|
|
// would just be replaced with their defaults similar to the apparent type.
|
|
if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) {
|
|
// For contextual signatures we incorporate all inferences made so far, e.g. from return
|
|
// types as well as arguments to the left in a function call.
|
|
if (contextFlags && contextFlags & ContextFlags.Signature) {
|
|
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
|
|
}
|
|
// For other purposes (e.g. determining whether to produce literal types) we only
|
|
// incorporate inferences made from the return type in a function call.
|
|
if (inferenceContext.returnMapper) {
|
|
return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
|
|
}
|
|
}
|
|
}
|
|
return contextualType;
|
|
}
|
|
|
|
// This function is similar to instantiateType, except that (a) it only instantiates types that
|
|
// are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs
|
|
// no reductions on instantiated union types.
|
|
function instantiateInstantiableTypes(type: Type, mapper: TypeMapper): Type {
|
|
if (type.flags & TypeFlags.Instantiable) {
|
|
return instantiateType(type, mapper);
|
|
}
|
|
if (type.flags & TypeFlags.Union) {
|
|
return getUnionType(map((<UnionType>type).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None);
|
|
}
|
|
if (type.flags & TypeFlags.Intersection) {
|
|
return getIntersectionType(map((<IntersectionType>type).types, t => instantiateInstantiableTypes(t, mapper)));
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Whoa! Do you really want to use this function?
|
|
*
|
|
* Unless you're trying to get the *non-apparent* type for a
|
|
* value-literal type or you're authoring relevant portions of this algorithm,
|
|
* you probably meant to use 'getApparentTypeOfContextualType'.
|
|
* Otherwise this may not be very useful.
|
|
*
|
|
* In cases where you *are* working on this function, you should understand
|
|
* when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'.
|
|
*
|
|
* - Use 'getContextualType' when you are simply going to propagate the result to the expression.
|
|
* - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type.
|
|
*
|
|
* @param node the expression whose contextual type will be returned.
|
|
* @returns the contextual type of an expression.
|
|
*/
|
|
function getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined {
|
|
if (node.flags & NodeFlags.InWithStatement) {
|
|
// We cannot answer semantic questions within a with block, do not proceed any further
|
|
return undefined;
|
|
}
|
|
if (node.contextualType) {
|
|
return node.contextualType;
|
|
}
|
|
const { parent } = node;
|
|
switch (parent.kind) {
|
|
case SyntaxKind.VariableDeclaration:
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.BindingElement:
|
|
return getContextualTypeForInitializerExpression(node);
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.ReturnStatement:
|
|
return getContextualTypeForReturnExpression(node);
|
|
case SyntaxKind.YieldExpression:
|
|
return getContextualTypeForYieldOperand(<YieldExpression>parent);
|
|
case SyntaxKind.AwaitExpression:
|
|
return getContextualTypeForAwaitOperand(<AwaitExpression>parent);
|
|
case SyntaxKind.CallExpression:
|
|
if ((<CallExpression>parent).expression.kind === SyntaxKind.ImportKeyword) {
|
|
return stringType;
|
|
}
|
|
/* falls through */
|
|
case SyntaxKind.NewExpression:
|
|
return getContextualTypeForArgument(<CallExpression | NewExpression>parent, node);
|
|
case SyntaxKind.TypeAssertionExpression:
|
|
case SyntaxKind.AsExpression:
|
|
return isConstTypeReference((<AssertionExpression>parent).type) ? undefined : getTypeFromTypeNode((<AssertionExpression>parent).type);
|
|
case SyntaxKind.BinaryExpression:
|
|
return getContextualTypeForBinaryOperand(node, contextFlags);
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
return getContextualTypeForObjectLiteralElement(<PropertyAssignment | ShorthandPropertyAssignment>parent, contextFlags);
|
|
case SyntaxKind.SpreadAssignment:
|
|
return getApparentTypeOfContextualType(parent.parent as ObjectLiteralExpression, contextFlags);
|
|
case SyntaxKind.ArrayLiteralExpression: {
|
|
const arrayLiteral = <ArrayLiteralExpression>parent;
|
|
const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags);
|
|
return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node));
|
|
}
|
|
case SyntaxKind.ConditionalExpression:
|
|
return getContextualTypeForConditionalOperand(node, contextFlags);
|
|
case SyntaxKind.TemplateSpan:
|
|
Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression);
|
|
return getContextualTypeForSubstitutionExpression(<TemplateExpression>parent.parent, node);
|
|
case SyntaxKind.ParenthesizedExpression: {
|
|
// Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
|
|
const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined;
|
|
return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(<ParenthesizedExpression>parent, contextFlags);
|
|
}
|
|
case SyntaxKind.JsxExpression:
|
|
return getContextualTypeForJsxExpression(<JsxExpression>parent);
|
|
case SyntaxKind.JsxAttribute:
|
|
case SyntaxKind.JsxSpreadAttribute:
|
|
return getContextualTypeForJsxAttribute(<JsxAttribute | JsxSpreadAttribute>parent);
|
|
case SyntaxKind.JsxOpeningElement:
|
|
case SyntaxKind.JsxSelfClosingElement:
|
|
return getContextualJsxElementAttributesType(<JsxOpeningLikeElement>parent, contextFlags);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getInferenceContext(node: Node) {
|
|
const ancestor = findAncestor(node, n => !!n.inferenceContext);
|
|
return ancestor && ancestor.inferenceContext!;
|
|
}
|
|
|
|
function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags?: ContextFlags) {
|
|
if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) {
|
|
// Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit
|
|
// _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type
|
|
// (as below) instead!
|
|
return node.parent.contextualType;
|
|
}
|
|
return getContextualTypeForArgumentAtIndex(node, 0);
|
|
}
|
|
|
|
function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) {
|
|
return getJsxReferenceKind(node) !== JsxReferenceKind.Component
|
|
? getJsxPropsTypeFromCallSignature(signature, node)
|
|
: getJsxPropsTypeFromClassType(signature, node);
|
|
}
|
|
|
|
function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) {
|
|
let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType);
|
|
propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType);
|
|
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
|
|
if (intrinsicAttribs !== errorType) {
|
|
propsType = intersectTypes(intrinsicAttribs, propsType);
|
|
}
|
|
return propsType;
|
|
}
|
|
|
|
function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) {
|
|
if (sig.unionSignatures) {
|
|
// JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input
|
|
// instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature,
|
|
// get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur
|
|
// for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input.
|
|
// The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane.
|
|
const results: Type[] = [];
|
|
for (const signature of sig.unionSignatures) {
|
|
const instance = getReturnTypeOfSignature(signature);
|
|
if (isTypeAny(instance)) {
|
|
return instance;
|
|
}
|
|
const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation);
|
|
if (!propType) {
|
|
return;
|
|
}
|
|
results.push(propType);
|
|
}
|
|
return getIntersectionType(results);
|
|
}
|
|
const instanceType = getReturnTypeOfSignature(sig);
|
|
return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation);
|
|
}
|
|
|
|
function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) {
|
|
if (isJsxIntrinsicIdentifier(context.tagName)) {
|
|
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context);
|
|
const fakeSignature = createSignatureForJSXIntrinsic(context, result);
|
|
return getOrCreateTypeFromSignature(fakeSignature);
|
|
}
|
|
const tagType = checkExpressionCached(context.tagName);
|
|
if (tagType.flags & TypeFlags.StringLiteral) {
|
|
const result = getIntrinsicAttributesTypeFromStringLiteralType(tagType as StringLiteralType, context);
|
|
if (!result) {
|
|
return errorType;
|
|
}
|
|
const fakeSignature = createSignatureForJSXIntrinsic(context, result);
|
|
return getOrCreateTypeFromSignature(fakeSignature);
|
|
}
|
|
return tagType;
|
|
}
|
|
|
|
function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) {
|
|
const managedSym = getJsxLibraryManagedAttributes(ns);
|
|
if (managedSym) {
|
|
const declaredManagedType = getDeclaredTypeOfSymbol(managedSym);
|
|
const ctorType = getStaticTypeOfReferencedJsxConstructor(context);
|
|
if (length((declaredManagedType as GenericType).typeParameters) >= 2) {
|
|
const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context));
|
|
return createTypeReference((declaredManagedType as GenericType), args);
|
|
}
|
|
else if (length(declaredManagedType.aliasTypeArguments) >= 2) {
|
|
const args = fillMissingTypeArguments([ctorType, attributesType], declaredManagedType.aliasTypeArguments, 2, isInJSFile(context));
|
|
return getTypeAliasInstantiation(declaredManagedType.aliasSymbol!, args);
|
|
}
|
|
}
|
|
return attributesType;
|
|
}
|
|
|
|
function getJsxPropsTypeFromClassType(sig: Signature, context: JsxOpeningLikeElement) {
|
|
const ns = getJsxNamespaceAt(context);
|
|
const forcedLookupLocation = getJsxElementPropertiesName(ns);
|
|
let attributesType = forcedLookupLocation === undefined
|
|
// If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type
|
|
? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType)
|
|
: forcedLookupLocation === ""
|
|
// If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead
|
|
? getReturnTypeOfSignature(sig)
|
|
// Otherwise get the type of the property on the signature return type
|
|
: getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation);
|
|
|
|
if (!attributesType) {
|
|
// There is no property named 'props' on this instance type
|
|
if (!!forcedLookupLocation && !!length(context.attributes.properties)) {
|
|
error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation));
|
|
}
|
|
return unknownType;
|
|
}
|
|
|
|
attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType);
|
|
|
|
if (isTypeAny(attributesType)) {
|
|
// Props is of type 'any' or unknown
|
|
return attributesType;
|
|
}
|
|
else {
|
|
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements
|
|
let apparentAttributesType = attributesType;
|
|
const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context);
|
|
if (intrinsicClassAttribs !== errorType) {
|
|
const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
|
|
const hostClassType = getReturnTypeOfSignature(sig);
|
|
apparentAttributesType = intersectTypes(
|
|
typeParams
|
|
? createTypeReference(<GenericType>intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context)))
|
|
: intrinsicClassAttribs,
|
|
apparentAttributesType
|
|
);
|
|
}
|
|
|
|
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
|
|
if (intrinsicAttribs !== errorType) {
|
|
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
|
|
}
|
|
|
|
return apparentAttributesType;
|
|
}
|
|
}
|
|
|
|
// If the given type is an object or union type with a single signature, and if that signature has at
|
|
// least as many parameters as the given function, return the signature. Otherwise return undefined.
|
|
function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined {
|
|
const signatures = getSignaturesOfType(type, SignatureKind.Call);
|
|
if (signatures.length === 1) {
|
|
const signature = signatures[0];
|
|
if (!isAritySmaller(signature, node)) {
|
|
return signature;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** If the contextual signature has fewer parameters than the function expression, do not use it */
|
|
function isAritySmaller(signature: Signature, target: SignatureDeclaration) {
|
|
let targetParameterCount = 0;
|
|
for (; targetParameterCount < target.parameters.length; targetParameterCount++) {
|
|
const param = target.parameters[targetParameterCount];
|
|
if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) {
|
|
break;
|
|
}
|
|
}
|
|
if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) {
|
|
targetParameterCount--;
|
|
}
|
|
return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount;
|
|
}
|
|
|
|
function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction {
|
|
return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction;
|
|
}
|
|
|
|
function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined {
|
|
// Only function expressions, arrow functions, and object literal methods are contextually typed.
|
|
return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node)
|
|
? getContextualSignature(<FunctionExpression>node)
|
|
: undefined;
|
|
}
|
|
|
|
// Return the contextual signature for a given expression node. A contextual type provides a
|
|
// contextual signature if it has a single call signature and if that call signature is non-generic.
|
|
// If the contextual type is a union type, get the signature from each type possible and if they are
|
|
// all identical ignoring their return type, the result is same signature but with return type as
|
|
// union type of return types from these signatures
|
|
function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined {
|
|
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
|
|
const typeTagSignature = getSignatureOfTypeTag(node);
|
|
if (typeTagSignature) {
|
|
return typeTagSignature;
|
|
}
|
|
const type = getApparentTypeOfContextualType(node, ContextFlags.Signature);
|
|
if (!type) {
|
|
return undefined;
|
|
}
|
|
if (!(type.flags & TypeFlags.Union)) {
|
|
return getContextualCallSignature(type, node);
|
|
}
|
|
let signatureList: Signature[] | undefined;
|
|
const types = (<UnionType>type).types;
|
|
for (const current of types) {
|
|
const signature = getContextualCallSignature(current, node);
|
|
if (signature) {
|
|
if (!signatureList) {
|
|
// This signature will contribute to contextual union signature
|
|
signatureList = [signature];
|
|
}
|
|
else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) {
|
|
// Signatures aren't identical, do not use
|
|
return undefined;
|
|
}
|
|
else {
|
|
// Use this signature for contextual union signature
|
|
signatureList.push(signature);
|
|
}
|
|
}
|
|
}
|
|
// Result is union of signatures collected (return type is union of return types of this signature set)
|
|
if (signatureList) {
|
|
return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList);
|
|
}
|
|
}
|
|
|
|
function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type {
|
|
if (languageVersion < ScriptTarget.ES2015) {
|
|
checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArrays);
|
|
}
|
|
|
|
const arrayOrIterableType = checkExpression(node.expression, checkMode);
|
|
return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression);
|
|
}
|
|
|
|
function hasDefaultValue(node: BindingElement | Expression): boolean {
|
|
return (node.kind === SyntaxKind.BindingElement && !!(<BindingElement>node).initializer) ||
|
|
(node.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node).operatorToken.kind === SyntaxKind.EqualsToken);
|
|
}
|
|
|
|
function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type {
|
|
const elements = node.elements;
|
|
const elementCount = elements.length;
|
|
const elementTypes: Type[] = [];
|
|
let hasEndingSpreadElement = false;
|
|
let hasNonEndingSpreadElement = false;
|
|
const contextualType = getApparentTypeOfContextualType(node);
|
|
const inDestructuringPattern = isAssignmentTarget(node);
|
|
const inConstContext = isConstContext(node);
|
|
for (let i = 0; i < elementCount; i++) {
|
|
const e = elements[i];
|
|
const spread = e.kind === SyntaxKind.SpreadElement && (<SpreadElement>e).expression;
|
|
const spreadType = spread && checkExpression(spread, checkMode, forceTuple);
|
|
if (spreadType && isTupleType(spreadType)) {
|
|
elementTypes.push(...getTypeArguments(spreadType));
|
|
if (spreadType.target.hasRestElement) {
|
|
if (i === elementCount - 1) hasEndingSpreadElement = true;
|
|
else hasNonEndingSpreadElement = true;
|
|
}
|
|
}
|
|
else {
|
|
if (inDestructuringPattern && spreadType) {
|
|
// Given the following situation:
|
|
// var c: {};
|
|
// [...c] = ["", 0];
|
|
//
|
|
// c is represented in the tree as a spread element in an array literal.
|
|
// But c really functions as a rest element, and its purpose is to provide
|
|
// a contextual type for the right hand side of the assignment. Therefore,
|
|
// instead of calling checkExpression on "...c", which will give an error
|
|
// if c is not iterable/array-like, we need to act as if we are trying to
|
|
// get the contextual element type from it. So we do something similar to
|
|
// getContextualTypeForElementExpression, which will crucially not error
|
|
// if there is no index type / iterated type.
|
|
const restElementType = getIndexTypeOfType(spreadType, IndexKind.Number) ||
|
|
getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false);
|
|
if (restElementType) {
|
|
elementTypes.push(restElementType);
|
|
}
|
|
}
|
|
else {
|
|
const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length);
|
|
const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple);
|
|
elementTypes.push(type);
|
|
}
|
|
if (spread) { // tuples are done above, so these are only arrays
|
|
if (i === elementCount - 1) hasEndingSpreadElement = true;
|
|
else hasNonEndingSpreadElement = true;
|
|
}
|
|
}
|
|
}
|
|
if (!hasNonEndingSpreadElement) {
|
|
const minLength = elementTypes.length - (hasEndingSpreadElement ? 1 : 0);
|
|
// If array literal is actually a destructuring pattern, mark it as an implied type. We do this such
|
|
// that we get the same behavior for "var [x, y] = []" and "[x, y] = []".
|
|
let tupleResult;
|
|
if (inDestructuringPattern && minLength > 0) {
|
|
const type = cloneTypeReference(<TypeReference>createTupleType(elementTypes, minLength, hasEndingSpreadElement));
|
|
type.pattern = node;
|
|
return type;
|
|
}
|
|
else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasEndingSpreadElement, elementTypes.length, inConstContext)) {
|
|
return createArrayLiteralType(tupleResult);
|
|
}
|
|
else if (forceTuple) {
|
|
return createArrayLiteralType(createTupleType(elementTypes, minLength, hasEndingSpreadElement));
|
|
}
|
|
}
|
|
return createArrayLiteralType(createArrayType(elementTypes.length ?
|
|
getUnionType(elementTypes, UnionReduction.Subtype) :
|
|
strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext));
|
|
}
|
|
|
|
function createArrayLiteralType(type: ObjectType) {
|
|
if (!(getObjectFlags(type) & ObjectFlags.Reference)) {
|
|
return type;
|
|
}
|
|
let literalType = (<TypeReference>type).literalType;
|
|
if (!literalType) {
|
|
literalType = (<TypeReference>type).literalType = cloneTypeReference(<TypeReference>type);
|
|
literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
|
|
}
|
|
return literalType;
|
|
}
|
|
|
|
function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length, readonly = false) {
|
|
// Infer a tuple type when the contextual type is or contains a tuple-like type
|
|
if (readonly || (contextualType && forEachType(contextualType, isTupleLikeType))) {
|
|
return createTupleType(elementTypes, elementCount - (hasRestElement ? 1 : 0), hasRestElement, readonly);
|
|
}
|
|
}
|
|
|
|
function isNumericName(name: DeclarationName): boolean {
|
|
switch (name.kind) {
|
|
case SyntaxKind.ComputedPropertyName:
|
|
return isNumericComputedName(name);
|
|
case SyntaxKind.Identifier:
|
|
return isNumericLiteralName(name.escapedText);
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.StringLiteral:
|
|
return isNumericLiteralName(name.text);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isNumericComputedName(name: ComputedPropertyName): boolean {
|
|
// It seems odd to consider an expression of type Any to result in a numeric name,
|
|
// but this behavior is consistent with checkIndexedAccess
|
|
return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike);
|
|
}
|
|
|
|
function isInfinityOrNaNString(name: string | __String): boolean {
|
|
return name === "Infinity" || name === "-Infinity" || name === "NaN";
|
|
}
|
|
|
|
function isNumericLiteralName(name: string | __String) {
|
|
// The intent of numeric names is that
|
|
// - they are names with text in a numeric form, and that
|
|
// - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit',
|
|
// acquired by applying the abstract 'ToNumber' operation on the name's text.
|
|
//
|
|
// The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name.
|
|
// In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold.
|
|
//
|
|
// Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)'
|
|
// according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'.
|
|
// Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names
|
|
// because their 'ToString' representation is not equal to their original text.
|
|
// This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1.
|
|
//
|
|
// Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'.
|
|
// The '+' prefix operator is equivalent here to applying the abstract ToNumber operation.
|
|
// Applying the 'toString()' method on a number gives us the abstract ToString operation on a number.
|
|
//
|
|
// Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional.
|
|
// This is desired behavior, because when indexing with them as numeric entities, you are indexing
|
|
// with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively.
|
|
return (+name).toString() === name;
|
|
}
|
|
|
|
function checkComputedPropertyName(node: ComputedPropertyName): Type {
|
|
const links = getNodeLinks(node.expression);
|
|
if (!links.resolvedType) {
|
|
links.resolvedType = checkExpression(node.expression);
|
|
// This will allow types number, string, symbol or any. It will also allow enums, the unknown
|
|
// type, and any union of these types (like string | number).
|
|
if (links.resolvedType.flags & TypeFlags.Nullable ||
|
|
!isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) &&
|
|
!isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) {
|
|
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any);
|
|
}
|
|
else {
|
|
checkThatExpressionIsProperSymbolReference(node.expression, links.resolvedType, /*reportError*/ true);
|
|
}
|
|
}
|
|
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], kind: IndexKind): IndexInfo {
|
|
const propTypes: Type[] = [];
|
|
for (let i = 0; i < properties.length; i++) {
|
|
if (kind === IndexKind.String || isNumericName(node.properties[i + offset].name!)) {
|
|
propTypes.push(getTypeOfSymbol(properties[i]));
|
|
}
|
|
}
|
|
const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType;
|
|
return createIndexInfo(unionType, isConstContext(node));
|
|
}
|
|
|
|
function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined {
|
|
Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here.");
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.immediateTarget) {
|
|
const node = getDeclarationOfAliasSymbol(symbol);
|
|
if (!node) return Debug.fail();
|
|
links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true);
|
|
}
|
|
|
|
return links.immediateTarget;
|
|
}
|
|
|
|
function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type {
|
|
const inDestructuringPattern = isAssignmentTarget(node);
|
|
// Grammar checking
|
|
checkGrammarObjectLiteralExpression(node, inDestructuringPattern);
|
|
|
|
let propertiesTable: SymbolTable;
|
|
const allPropertiesTable = createSymbolTable();
|
|
let propertiesArray: Symbol[] = [];
|
|
let spread: Type = emptyObjectType;
|
|
|
|
const contextualType = getApparentTypeOfContextualType(node);
|
|
const contextualTypeHasPattern = contextualType && contextualType.pattern &&
|
|
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
|
|
const inConstContext = isConstContext(node);
|
|
const checkFlags = inConstContext ? CheckFlags.Readonly : 0;
|
|
const isInJavascript = isInJSFile(node) && !isInJsonFile(node);
|
|
const enumTag = getJSDocEnumTag(node);
|
|
const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag;
|
|
let objectFlags: ObjectFlags = freshObjectLiteralFlag;
|
|
let patternWithComputedProperties = false;
|
|
let hasComputedStringProperty = false;
|
|
let hasComputedNumberProperty = false;
|
|
propertiesTable = createSymbolTable();
|
|
|
|
let offset = 0;
|
|
for (let i = 0; i < node.properties.length; i++) {
|
|
const memberDecl = node.properties[i];
|
|
let member = getSymbolOfNode(memberDecl);
|
|
const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ?
|
|
checkComputedPropertyName(memberDecl.name) : undefined;
|
|
if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
|
|
memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ||
|
|
isObjectLiteralMethod(memberDecl)) {
|
|
let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) :
|
|
memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(memberDecl.name, checkMode) :
|
|
checkObjectLiteralMethod(memberDecl, checkMode);
|
|
if (isInJavascript) {
|
|
const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl);
|
|
if (jsDocType) {
|
|
checkTypeAssignableTo(type, jsDocType, memberDecl);
|
|
type = jsDocType;
|
|
}
|
|
else if (enumTag && enumTag.typeExpression) {
|
|
checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl);
|
|
}
|
|
}
|
|
objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags;
|
|
const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined;
|
|
const prop = nameType ?
|
|
createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) :
|
|
createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags);
|
|
if (nameType) {
|
|
prop.nameType = nameType;
|
|
}
|
|
|
|
if (inDestructuringPattern) {
|
|
// If object literal is an assignment pattern and if the assignment pattern specifies a default value
|
|
// for the property, make the property optional.
|
|
const isOptional =
|
|
(memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) ||
|
|
(memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer);
|
|
if (isOptional) {
|
|
prop.flags |= SymbolFlags.Optional;
|
|
}
|
|
}
|
|
else if (contextualTypeHasPattern && !(getObjectFlags(contextualType!) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
|
|
// If object literal is contextually typed by the implied type of a binding pattern, and if the
|
|
// binding pattern specifies a default value for the property, make the property optional.
|
|
const impliedProp = getPropertyOfType(contextualType!, member.escapedName);
|
|
if (impliedProp) {
|
|
prop.flags |= impliedProp.flags & SymbolFlags.Optional;
|
|
}
|
|
|
|
else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType(contextualType!, IndexKind.String)) {
|
|
error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
|
|
symbolToString(member), typeToString(contextualType!));
|
|
}
|
|
}
|
|
|
|
prop.declarations = member.declarations;
|
|
prop.parent = member.parent;
|
|
if (member.valueDeclaration) {
|
|
prop.valueDeclaration = member.valueDeclaration;
|
|
}
|
|
|
|
prop.type = type;
|
|
prop.target = member;
|
|
member = prop;
|
|
allPropertiesTable.set(prop.escapedName, prop);
|
|
}
|
|
else if (memberDecl.kind === SyntaxKind.SpreadAssignment) {
|
|
if (languageVersion < ScriptTarget.ES2015) {
|
|
checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign);
|
|
}
|
|
if (propertiesArray.length > 0) {
|
|
spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext);
|
|
propertiesArray = [];
|
|
propertiesTable = createSymbolTable();
|
|
hasComputedStringProperty = false;
|
|
hasComputedNumberProperty = false;
|
|
}
|
|
const type = getReducedType(checkExpression(memberDecl.expression));
|
|
if (!isValidSpreadType(type)) {
|
|
error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types);
|
|
return errorType;
|
|
}
|
|
for (const right of getPropertiesOfType(type)) {
|
|
const rightType = getTypeOfSymbol(right);
|
|
const left = allPropertiesTable.get(right.escapedName);
|
|
if (strictNullChecks &&
|
|
left &&
|
|
!maybeTypeOfKind(rightType, TypeFlags.Nullable)) {
|
|
error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName));
|
|
}
|
|
}
|
|
|
|
spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext);
|
|
offset = i + 1;
|
|
continue;
|
|
}
|
|
else {
|
|
// TypeScript 1.0 spec (April 2014)
|
|
// A get accessor declaration is processed in the same manner as
|
|
// an ordinary function declaration(section 6.1) with no parameters.
|
|
// A set accessor declaration is processed in the same manner
|
|
// as an ordinary function declaration with a single parameter and a Void return type.
|
|
Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor);
|
|
checkNodeDeferred(memberDecl);
|
|
}
|
|
|
|
if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) {
|
|
if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) {
|
|
if (isTypeAssignableTo(computedNameType, numberType)) {
|
|
hasComputedNumberProperty = true;
|
|
}
|
|
else {
|
|
hasComputedStringProperty = true;
|
|
}
|
|
if (inDestructuringPattern) {
|
|
patternWithComputedProperties = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
propertiesTable.set(member.escapedName, member);
|
|
}
|
|
propertiesArray.push(member);
|
|
}
|
|
|
|
// If object literal is contextually typed by the implied type of a binding pattern, augment the result
|
|
// type with those properties for which the binding pattern specifies a default value.
|
|
// If the object literal is spread into another object literal, skip this step and let the top-level object
|
|
// literal handle it instead.
|
|
if (contextualTypeHasPattern && node.parent.kind !== SyntaxKind.SpreadAssignment) {
|
|
for (const prop of getPropertiesOfType(contextualType!)) {
|
|
if (!propertiesTable.get(prop.escapedName) && !getPropertyOfType(spread, prop.escapedName)) {
|
|
if (!(prop.flags & SymbolFlags.Optional)) {
|
|
error(prop.valueDeclaration || (<TransientSymbol>prop).bindingElement,
|
|
Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
|
|
}
|
|
propertiesTable.set(prop.escapedName, prop);
|
|
propertiesArray.push(prop);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (spread !== emptyObjectType) {
|
|
if (propertiesArray.length > 0) {
|
|
spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext);
|
|
propertiesArray = [];
|
|
propertiesTable = createSymbolTable();
|
|
hasComputedStringProperty = false;
|
|
hasComputedNumberProperty = false;
|
|
}
|
|
// remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site
|
|
return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t);
|
|
}
|
|
|
|
return createObjectLiteralType();
|
|
|
|
function createObjectLiteralType() {
|
|
const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.String) : undefined;
|
|
const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.Number) : undefined;
|
|
const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
|
|
result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
|
|
if (isJSObjectLiteral) {
|
|
result.objectFlags |= ObjectFlags.JSLiteral;
|
|
}
|
|
if (patternWithComputedProperties) {
|
|
result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties;
|
|
}
|
|
if (inDestructuringPattern) {
|
|
result.pattern = node;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
function isValidSpreadType(type: Type): boolean {
|
|
if (type.flags & TypeFlags.Instantiable) {
|
|
const constraint = getBaseConstraintOfType(type);
|
|
if (constraint !== undefined) {
|
|
return isValidSpreadType(constraint);
|
|
}
|
|
}
|
|
return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ||
|
|
getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) ||
|
|
type.flags & TypeFlags.UnionOrIntersection && every((<UnionOrIntersectionType>type).types, isValidSpreadType));
|
|
}
|
|
|
|
function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) {
|
|
checkJsxOpeningLikeElementOrOpeningFragment(node);
|
|
resolveUntypedCall(node); // ensure type arguments and parameters are typechecked, even if there is an arity error
|
|
}
|
|
|
|
function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type {
|
|
checkNodeDeferred(node);
|
|
return getJsxElementTypeAt(node) || anyType;
|
|
}
|
|
|
|
function checkJsxElementDeferred(node: JsxElement) {
|
|
// Check attributes
|
|
checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement);
|
|
|
|
// Perform resolution on the closing tag so that rename/go to definition/etc work
|
|
if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) {
|
|
getIntrinsicTagSymbol(node.closingElement);
|
|
}
|
|
else {
|
|
checkExpression(node.closingElement.tagName);
|
|
}
|
|
|
|
checkJsxChildren(node);
|
|
}
|
|
|
|
function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type {
|
|
checkNodeDeferred(node);
|
|
|
|
return getJsxElementTypeAt(node) || anyType;
|
|
}
|
|
|
|
function checkJsxFragment(node: JsxFragment): Type {
|
|
checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment);
|
|
|
|
if (compilerOptions.jsx === JsxEmit.React && (compilerOptions.jsxFactory || getSourceFileOfNode(node).pragmas.has("jsx"))) {
|
|
error(node, compilerOptions.jsxFactory
|
|
? Diagnostics.JSX_fragment_is_not_supported_when_using_jsxFactory
|
|
: Diagnostics.JSX_fragment_is_not_supported_when_using_an_inline_JSX_factory_pragma);
|
|
}
|
|
|
|
checkJsxChildren(node);
|
|
return getJsxElementTypeAt(node) || anyType;
|
|
}
|
|
|
|
/**
|
|
* Returns true iff the JSX element name would be a valid JS identifier, ignoring restrictions about keywords not being identifiers
|
|
*/
|
|
function isUnhyphenatedJsxName(name: string | __String) {
|
|
// - is the only character supported in JSX attribute names that isn't valid in JavaScript identifiers
|
|
return !stringContains(name as string, "-");
|
|
}
|
|
|
|
/**
|
|
* Returns true iff React would emit this tag name as a string rather than an identifier or qualified name
|
|
*/
|
|
function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean {
|
|
return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText);
|
|
}
|
|
|
|
function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) {
|
|
return node.initializer
|
|
? checkExpressionForMutableLocation(node.initializer, checkMode)
|
|
: trueType; // <Elem attr /> is sugar for <Elem attr={true} />
|
|
}
|
|
|
|
/**
|
|
* Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element.
|
|
*
|
|
* @param openingLikeElement a JSX opening-like element
|
|
* @param filter a function to remove attributes that will not participate in checking whether attributes are assignable
|
|
* @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property.
|
|
* @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral,
|
|
* which also calls getSpreadType.
|
|
*/
|
|
function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) {
|
|
const attributes = openingLikeElement.attributes;
|
|
let attributesTable = createSymbolTable();
|
|
let spread: Type = emptyJsxObjectType;
|
|
let hasSpreadAnyType = false;
|
|
let typeToIntersect: Type | undefined;
|
|
let explicitlySpecifyChildrenAttribute = false;
|
|
let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes;
|
|
const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement));
|
|
|
|
for (const attributeDecl of attributes.properties) {
|
|
const member = attributeDecl.symbol;
|
|
if (isJsxAttribute(attributeDecl)) {
|
|
const exprType = checkJsxAttribute(attributeDecl, checkMode);
|
|
objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags;
|
|
|
|
const attributeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.escapedName);
|
|
attributeSymbol.declarations = member.declarations;
|
|
attributeSymbol.parent = member.parent;
|
|
if (member.valueDeclaration) {
|
|
attributeSymbol.valueDeclaration = member.valueDeclaration;
|
|
}
|
|
attributeSymbol.type = exprType;
|
|
attributeSymbol.target = member;
|
|
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
|
|
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
|
|
explicitlySpecifyChildrenAttribute = true;
|
|
}
|
|
}
|
|
else {
|
|
Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute);
|
|
if (attributesTable.size > 0) {
|
|
spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false);
|
|
attributesTable = createSymbolTable();
|
|
}
|
|
const exprType = getReducedType(checkExpressionCached(attributeDecl.expression, checkMode));
|
|
if (isTypeAny(exprType)) {
|
|
hasSpreadAnyType = true;
|
|
}
|
|
if (isValidSpreadType(exprType)) {
|
|
spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false);
|
|
}
|
|
else {
|
|
typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hasSpreadAnyType) {
|
|
if (attributesTable.size > 0) {
|
|
spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false);
|
|
}
|
|
}
|
|
|
|
// Handle children attribute
|
|
const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined;
|
|
// We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement
|
|
if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) {
|
|
const childrenTypes: Type[] = checkJsxChildren(parent, checkMode);
|
|
|
|
if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
|
|
// Error if there is a attribute named "children" explicitly specified and children element.
|
|
// This is because children element will overwrite the value from attributes.
|
|
// Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread.
|
|
if (explicitlySpecifyChildrenAttribute) {
|
|
error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName));
|
|
}
|
|
|
|
const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes);
|
|
const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
|
|
// If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process
|
|
const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName);
|
|
childrenPropSymbol.type = childrenTypes.length === 1 ?
|
|
childrenTypes[0] :
|
|
(getArrayLiteralTupleTypeIfApplicable(childrenTypes, childrenContextualType, /*hasRestElement*/ false) || createArrayType(getUnionType(childrenTypes)));
|
|
// Fake up a property declaration for the children
|
|
childrenPropSymbol.valueDeclaration = createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined);
|
|
childrenPropSymbol.valueDeclaration.parent = attributes;
|
|
childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol;
|
|
const childPropMap = createSymbolTable();
|
|
childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol);
|
|
spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined),
|
|
attributes.symbol, objectFlags, /*readonly*/ false);
|
|
|
|
}
|
|
}
|
|
|
|
if (hasSpreadAnyType) {
|
|
return anyType;
|
|
}
|
|
if (typeToIntersect && spread !== emptyJsxObjectType) {
|
|
return getIntersectionType([typeToIntersect, spread]);
|
|
}
|
|
return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread);
|
|
|
|
/**
|
|
* Create anonymous type from given attributes symbol table.
|
|
* @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable
|
|
* @param attributesTable a symbol table of attributes property
|
|
*/
|
|
function createJsxAttributesType() {
|
|
objectFlags |= freshObjectLiteralFlag;
|
|
const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
|
|
result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) {
|
|
const childrenTypes: Type[] = [];
|
|
for (const child of node.children) {
|
|
// In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that
|
|
// because then type of children property will have constituent of string type.
|
|
if (child.kind === SyntaxKind.JsxText) {
|
|
if (!child.containsOnlyTriviaWhiteSpaces) {
|
|
childrenTypes.push(stringType);
|
|
}
|
|
}
|
|
else {
|
|
childrenTypes.push(checkExpressionForMutableLocation(child, checkMode));
|
|
}
|
|
}
|
|
return childrenTypes;
|
|
}
|
|
|
|
/**
|
|
* Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element.
|
|
* (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used)
|
|
* @param node a JSXAttributes to be resolved of its type
|
|
*/
|
|
function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) {
|
|
return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode);
|
|
}
|
|
|
|
function getJsxType(name: __String, location: Node | undefined) {
|
|
const namespace = getJsxNamespaceAt(location);
|
|
const exports = namespace && getExportsOfSymbol(namespace);
|
|
const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type);
|
|
return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType;
|
|
}
|
|
|
|
/**
|
|
* Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic
|
|
* property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic
|
|
* string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement).
|
|
* May also return unknownSymbol if both of these lookups fail.
|
|
*/
|
|
function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedSymbol) {
|
|
const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node);
|
|
if (intrinsicElementsType !== errorType) {
|
|
// Property case
|
|
if (!isIdentifier(node.tagName)) return Debug.fail();
|
|
const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText);
|
|
if (intrinsicProp) {
|
|
links.jsxFlags |= JsxFlags.IntrinsicNamedElement;
|
|
return links.resolvedSymbol = intrinsicProp;
|
|
}
|
|
|
|
// Intrinsic string indexer case
|
|
const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String);
|
|
if (indexSignatureType) {
|
|
links.jsxFlags |= JsxFlags.IntrinsicIndexedElement;
|
|
return links.resolvedSymbol = intrinsicElementsType.symbol;
|
|
}
|
|
|
|
// Wasn't found
|
|
error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements);
|
|
return links.resolvedSymbol = unknownSymbol;
|
|
}
|
|
else {
|
|
if (noImplicitAny) {
|
|
error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements));
|
|
}
|
|
return links.resolvedSymbol = unknownSymbol;
|
|
}
|
|
}
|
|
return links.resolvedSymbol;
|
|
}
|
|
|
|
function getJsxNamespaceAt(location: Node | undefined): Symbol {
|
|
const links = location && getNodeLinks(location);
|
|
if (links && links.jsxNamespace) {
|
|
return links.jsxNamespace;
|
|
}
|
|
if (!links || links.jsxNamespace !== false) {
|
|
const namespaceName = getJsxNamespace(location);
|
|
const resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false);
|
|
if (resolvedNamespace) {
|
|
const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace));
|
|
if (candidate) {
|
|
if (links) {
|
|
links.jsxNamespace = candidate;
|
|
}
|
|
return candidate;
|
|
}
|
|
if (links) {
|
|
links.jsxNamespace = false;
|
|
}
|
|
}
|
|
}
|
|
// JSX global fallback
|
|
return getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined)!; // TODO: GH#18217
|
|
}
|
|
|
|
/**
|
|
* Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer.
|
|
* Get a single property from that container if existed. Report an error if there are more than one property.
|
|
*
|
|
* @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer
|
|
* if other string is given or the container doesn't exist, return undefined.
|
|
*/
|
|
function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: Symbol): __String | undefined {
|
|
// JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol]
|
|
const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol(jsxNamespace.exports!, nameOfAttribPropContainer, SymbolFlags.Type);
|
|
// JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type]
|
|
const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym);
|
|
// The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute
|
|
const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType);
|
|
if (propertiesOfJsxElementAttribPropInterface) {
|
|
// Element Attributes has zero properties, so the element attributes type will be the class instance type
|
|
if (propertiesOfJsxElementAttribPropInterface.length === 0) {
|
|
return "" as __String;
|
|
}
|
|
// Element Attributes has one property, so the element attributes type will be the type of the corresponding
|
|
// property of the class instance type
|
|
else if (propertiesOfJsxElementAttribPropInterface.length === 1) {
|
|
return propertiesOfJsxElementAttribPropInterface[0].escapedName;
|
|
}
|
|
else if (propertiesOfJsxElementAttribPropInterface.length > 1) {
|
|
// More than one property on ElementAttributesProperty is an error
|
|
error(jsxElementAttribPropInterfaceSym!.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer));
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) {
|
|
// JSX.LibraryManagedAttributes [symbol]
|
|
return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type);
|
|
}
|
|
|
|
/// e.g. "props" for React.d.ts,
|
|
/// or 'undefined' if ElementAttributesProperty doesn't exist (which means all
|
|
/// non-intrinsic elements' attributes type is 'any'),
|
|
/// or '' if it has 0 properties (which means every
|
|
/// non-intrinsic elements' attributes type is the element instance type)
|
|
function getJsxElementPropertiesName(jsxNamespace: Symbol) {
|
|
return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace);
|
|
}
|
|
|
|
function getJsxElementChildrenPropertyName(jsxNamespace: Symbol): __String | undefined {
|
|
return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace);
|
|
}
|
|
|
|
function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] {
|
|
if (elementType.flags & TypeFlags.String) {
|
|
return [anySignature];
|
|
}
|
|
else if (elementType.flags & TypeFlags.StringLiteral) {
|
|
const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller);
|
|
if (!intrinsicType) {
|
|
error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements);
|
|
return emptyArray;
|
|
}
|
|
else {
|
|
const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType);
|
|
return [fakeSignature];
|
|
}
|
|
}
|
|
const apparentElemType = getApparentType(elementType);
|
|
// Resolve the signatures, preferring constructor
|
|
let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct);
|
|
if (signatures.length === 0) {
|
|
// No construct signatures, try call signatures
|
|
signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call);
|
|
}
|
|
if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) {
|
|
// If each member has some combination of new/call signatures; make a union signature list for those
|
|
signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller)));
|
|
}
|
|
return signatures;
|
|
}
|
|
|
|
function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): Type | undefined {
|
|
// If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type
|
|
// For example:
|
|
// var CustomTag: "h1" = "h1";
|
|
// <CustomTag> Hello World </CustomTag>
|
|
const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location);
|
|
if (intrinsicElementsType !== errorType) {
|
|
const stringLiteralTypeName = type.value;
|
|
const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName));
|
|
if (intrinsicProp) {
|
|
return getTypeOfSymbol(intrinsicProp);
|
|
}
|
|
const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String);
|
|
if (indexSignatureType) {
|
|
return indexSignatureType;
|
|
}
|
|
return undefined;
|
|
}
|
|
// If we need to report an error, we already done so here. So just return any to prevent any more error downstream
|
|
return anyType;
|
|
}
|
|
|
|
function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: Node) {
|
|
if (refKind === JsxReferenceKind.Function) {
|
|
const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
|
|
if (sfcReturnConstraint) {
|
|
checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
|
|
}
|
|
}
|
|
else if (refKind === JsxReferenceKind.Component) {
|
|
const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
|
|
if (classConstraint) {
|
|
// Issue an error if this return type isn't assignable to JSX.ElementClass or JSX.Element, failing that
|
|
checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
|
|
}
|
|
}
|
|
else { // Mixed
|
|
const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
|
|
const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
|
|
if (!sfcReturnConstraint || !classConstraint) {
|
|
return;
|
|
}
|
|
const combined = getUnionType([sfcReturnConstraint, classConstraint]);
|
|
checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name.
|
|
* The function is intended to be called from a function which has checked that the opening element is an intrinsic element.
|
|
* @param node an intrinsic JSX opening-like element
|
|
*/
|
|
function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type {
|
|
Debug.assert(isJsxIntrinsicIdentifier(node.tagName));
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedJsxElementAttributesType) {
|
|
const symbol = getIntrinsicTagSymbol(node);
|
|
if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) {
|
|
return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol);
|
|
}
|
|
else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) {
|
|
return links.resolvedJsxElementAttributesType =
|
|
getIndexTypeOfType(getDeclaredTypeOfSymbol(symbol), IndexKind.String)!;
|
|
}
|
|
else {
|
|
return links.resolvedJsxElementAttributesType = errorType;
|
|
}
|
|
}
|
|
return links.resolvedJsxElementAttributesType;
|
|
}
|
|
|
|
function getJsxElementClassTypeAt(location: Node): Type | undefined {
|
|
const type = getJsxType(JsxNames.ElementClass, location);
|
|
if (type === errorType) return undefined;
|
|
return type;
|
|
}
|
|
|
|
function getJsxElementTypeAt(location: Node): Type {
|
|
return getJsxType(JsxNames.Element, location);
|
|
}
|
|
|
|
function getJsxStatelessElementTypeAt(location: Node): Type | undefined {
|
|
const jsxElementType = getJsxElementTypeAt(location);
|
|
if (jsxElementType) {
|
|
return getUnionType([jsxElementType, nullType]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns all the properties of the Jsx.IntrinsicElements interface
|
|
*/
|
|
function getJsxIntrinsicTagNamesAt(location: Node): Symbol[] {
|
|
const intrinsics = getJsxType(JsxNames.IntrinsicElements, location);
|
|
return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray;
|
|
}
|
|
|
|
function checkJsxPreconditions(errorNode: Node) {
|
|
// Preconditions for using JSX
|
|
if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) {
|
|
error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided);
|
|
}
|
|
|
|
if (getJsxElementTypeAt(errorNode) === undefined) {
|
|
if (noImplicitAny) {
|
|
error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) {
|
|
const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node);
|
|
|
|
if (isNodeOpeningLikeElement) {
|
|
checkGrammarJsxElement(<JsxOpeningLikeElement>node);
|
|
}
|
|
checkJsxPreconditions(node);
|
|
// The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import.
|
|
// And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error.
|
|
const reactRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
|
|
const reactNamespace = getJsxNamespace(node);
|
|
const reactLocation = isNodeOpeningLikeElement ? (<JsxOpeningLikeElement>node).tagName : node;
|
|
const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
|
|
if (reactSym) {
|
|
// Mark local symbol as referenced here because it might not have been marked
|
|
// if jsx emit was not react as there wont be error being emitted
|
|
reactSym.isReferenced = SymbolFlags.All;
|
|
|
|
// If react symbol is alias, mark it as refereced
|
|
if (reactSym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(reactSym)) {
|
|
markAliasSymbolAsReferenced(reactSym);
|
|
}
|
|
}
|
|
|
|
if (isNodeOpeningLikeElement) {
|
|
const sig = getResolvedSignature(node as JsxOpeningLikeElement);
|
|
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node as JsxOpeningLikeElement), getReturnTypeOfSignature(sig), node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a property with the given name is known anywhere in the given type. In an object type, a property
|
|
* is considered known if
|
|
* 1. the object type is empty and the check is for assignability, or
|
|
* 2. if the object type has index signatures, or
|
|
* 3. if the property is actually declared in the object type
|
|
* (this means that 'toString', for example, is not usually a known property).
|
|
* 4. In a union or intersection type,
|
|
* a property is considered known if it is known in any constituent type.
|
|
* @param targetType a type to search a given name in
|
|
* @param name a property name to search
|
|
* @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType
|
|
*/
|
|
function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean {
|
|
if (targetType.flags & TypeFlags.Object) {
|
|
const resolved = resolveStructuredTypeMembers(targetType as ObjectType);
|
|
if (resolved.stringIndexInfo ||
|
|
resolved.numberIndexInfo && isNumericLiteralName(name) ||
|
|
getPropertyOfObjectType(targetType, name) ||
|
|
isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
|
|
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
|
|
return true;
|
|
}
|
|
}
|
|
else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) {
|
|
for (const t of (targetType as UnionOrIntersectionType).types) {
|
|
if (isKnownProperty(t, name, isComparingJsxAttributes)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isExcessPropertyCheckTarget(type: Type): boolean {
|
|
return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) ||
|
|
type.flags & TypeFlags.NonPrimitive ||
|
|
type.flags & TypeFlags.Union && some((<UnionType>type).types, isExcessPropertyCheckTarget) ||
|
|
type.flags & TypeFlags.Intersection && every((<IntersectionType>type).types, isExcessPropertyCheckTarget));
|
|
}
|
|
|
|
function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) {
|
|
checkGrammarJsxExpression(node);
|
|
if (node.expression) {
|
|
const type = checkExpression(node.expression, checkMode);
|
|
if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) {
|
|
error(node, Diagnostics.JSX_spread_child_must_be_an_array_type);
|
|
}
|
|
return type;
|
|
}
|
|
else {
|
|
return errorType;
|
|
}
|
|
}
|
|
|
|
function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags {
|
|
return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0;
|
|
}
|
|
|
|
/**
|
|
* Return whether this symbol is a member of a prototype somewhere
|
|
* Note that this is not tracked well within the compiler, so the answer may be incorrect.
|
|
*/
|
|
function isPrototypeProperty(symbol: Symbol) {
|
|
if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) {
|
|
return true;
|
|
}
|
|
if (isInJSFile(symbol.valueDeclaration)) {
|
|
const parent = symbol.valueDeclaration.parent;
|
|
return parent && isBinaryExpression(parent) &&
|
|
getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether the requested property access is valid.
|
|
* Returns true if node is a valid property access, and false otherwise.
|
|
* @param node The node to be checked.
|
|
* @param isSuper True if the access is from `super.`.
|
|
* @param type The type of the object whose property is being accessed. (Not the type of the property.)
|
|
* @param prop The symbol for the property being accessed.
|
|
*/
|
|
function checkPropertyAccessibility(
|
|
node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement,
|
|
isSuper: boolean, type: Type, prop: Symbol): boolean {
|
|
const flags = getDeclarationModifierFlagsFromSymbol(prop);
|
|
const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right : node.kind === SyntaxKind.ImportType ? node : node.name;
|
|
|
|
if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) {
|
|
// Synthetic property with private constituent property
|
|
error(errorNode, Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(prop), typeToString(type));
|
|
return false;
|
|
}
|
|
|
|
if (isSuper) {
|
|
// TS 1.0 spec (April 2014): 4.8.2
|
|
// - In a constructor, instance member function, instance member accessor, or
|
|
// instance member variable initializer where this references a derived class instance,
|
|
// a super property access is permitted and must specify a public instance member function of the base class.
|
|
// - In a static member function or static member accessor
|
|
// where this references the constructor function object of a derived class,
|
|
// a super property access is permitted and must specify a public static member function of the base class.
|
|
if (languageVersion < ScriptTarget.ES2015) {
|
|
if (symbolHasNonMethodDeclaration(prop)) {
|
|
error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword);
|
|
return false;
|
|
}
|
|
}
|
|
if (flags & ModifierFlags.Abstract) {
|
|
// A method cannot be accessed in a super property access if the method is abstract.
|
|
// This error could mask a private property access error. But, a member
|
|
// cannot simultaneously be private and abstract, so this will trigger an
|
|
// additional error elsewhere.
|
|
error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Referencing abstract properties within their own constructors is not allowed
|
|
if ((flags & ModifierFlags.Abstract) && isThisProperty(node) && symbolHasNonMethodDeclaration(prop)) {
|
|
const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!);
|
|
if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(node)) {
|
|
error(errorNode, Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, symbolToString(prop), getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); // TODO: GH#18217
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (isPropertyAccessExpression(node) && isPrivateIdentifier(node.name)) {
|
|
if (!getContainingClass(node)) {
|
|
error(errorNode, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Public properties are otherwise accessible.
|
|
if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) {
|
|
return true;
|
|
}
|
|
|
|
// Property is known to be private or protected at this point
|
|
|
|
// Private property is accessible if the property is within the declaring class
|
|
if (flags & ModifierFlags.Private) {
|
|
const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!;
|
|
if (!isNodeWithinClass(node, declaringClassDeclaration)) {
|
|
error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(getDeclaringClass(prop)!));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Property is known to be protected at this point
|
|
|
|
// All protected properties of a supertype are accessible in a super access
|
|
if (isSuper) {
|
|
return true;
|
|
}
|
|
|
|
// Find the first enclosing class that has the declaring classes of the protected constituents
|
|
// of the property as base classes
|
|
let enclosingClass = forEachEnclosingClass(node, enclosingDeclaration => {
|
|
const enclosingClass = <InterfaceType>getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!);
|
|
return isClassDerivedFromDeclaringClasses(enclosingClass, prop) ? enclosingClass : undefined;
|
|
});
|
|
// A protected property is accessible if the property is within the declaring class or classes derived from it
|
|
if (!enclosingClass) {
|
|
// allow PropertyAccessibility if context is in function with this parameter
|
|
// static member access is disallow
|
|
let thisParameter: ParameterDeclaration | undefined;
|
|
if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(node)) || !thisParameter.type) {
|
|
error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || type));
|
|
return false;
|
|
}
|
|
|
|
const thisType = getTypeFromTypeNode(thisParameter.type);
|
|
enclosingClass = (((thisType.flags & TypeFlags.TypeParameter) ? getConstraintOfTypeParameter(<TypeParameter>thisType) : thisType) as TypeReference).target;
|
|
}
|
|
// No further restrictions for static properties
|
|
if (flags & ModifierFlags.Static) {
|
|
return true;
|
|
}
|
|
if (type.flags & TypeFlags.TypeParameter) {
|
|
// get the original type -- represented as the type constraint of the 'this' type
|
|
type = (type as TypeParameter).isThisType ? getConstraintOfTypeParameter(<TypeParameter>type)! : getBaseConstraintOfType(<TypeParameter>type)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined
|
|
}
|
|
if (!type || !hasBaseType(type, enclosingClass)) {
|
|
error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1, symbolToString(prop), typeToString(enclosingClass));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function getThisParameterFromNodeContext(node: Node) {
|
|
const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false);
|
|
return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined;
|
|
}
|
|
|
|
function symbolHasNonMethodDeclaration(symbol: Symbol) {
|
|
return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method));
|
|
}
|
|
|
|
function checkNonNullExpression(node: Expression | QualifiedName) {
|
|
return checkNonNullType(checkExpression(node), node);
|
|
}
|
|
|
|
function isNullableType(type: Type) {
|
|
return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable);
|
|
}
|
|
|
|
function getNonNullableTypeIfNeeded(type: Type) {
|
|
return isNullableType(type) ? getNonNullableType(type) : type;
|
|
}
|
|
|
|
function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) {
|
|
error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ?
|
|
Diagnostics.Object_is_possibly_null_or_undefined :
|
|
Diagnostics.Object_is_possibly_undefined :
|
|
Diagnostics.Object_is_possibly_null
|
|
);
|
|
}
|
|
|
|
function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) {
|
|
error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ?
|
|
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined :
|
|
Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined :
|
|
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null
|
|
);
|
|
}
|
|
|
|
function checkNonNullTypeWithReporter(
|
|
type: Type,
|
|
node: Node,
|
|
reportError: (node: Node, kind: TypeFlags) => void
|
|
): Type {
|
|
if (strictNullChecks && type.flags & TypeFlags.Unknown) {
|
|
error(node, Diagnostics.Object_is_of_type_unknown);
|
|
return errorType;
|
|
}
|
|
const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable;
|
|
if (kind) {
|
|
reportError(node, kind);
|
|
const t = getNonNullableType(type);
|
|
return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function checkNonNullType(type: Type, node: Node) {
|
|
return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError);
|
|
}
|
|
|
|
function checkNonNullNonVoidType(type: Type, node: Node): Type {
|
|
const nonNullType = checkNonNullType(type, node);
|
|
if (nonNullType !== errorType && nonNullType.flags & TypeFlags.Void) {
|
|
error(node, Diagnostics.Object_is_possibly_undefined);
|
|
}
|
|
return nonNullType;
|
|
}
|
|
|
|
function checkPropertyAccessExpression(node: PropertyAccessExpression) {
|
|
return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain) :
|
|
checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name);
|
|
}
|
|
|
|
function checkPropertyAccessChain(node: PropertyAccessChain) {
|
|
const leftType = checkExpression(node.expression);
|
|
const nonOptionalType = getOptionalExpressionType(leftType, node.expression);
|
|
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType);
|
|
}
|
|
|
|
function checkQualifiedName(node: QualifiedName) {
|
|
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right);
|
|
}
|
|
|
|
function isMethodAccessForCall(node: Node) {
|
|
while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
|
|
node = node.parent;
|
|
}
|
|
return isCallOrNewExpression(node.parent) && node.parent.expression === node;
|
|
}
|
|
|
|
// Lookup the private identifier lexically.
|
|
function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined {
|
|
for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) {
|
|
const { symbol } = containingClass;
|
|
const name = getSymbolNameForPrivateIdentifier(symbol, propName);
|
|
const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name));
|
|
if (prop) {
|
|
return prop;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined {
|
|
return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName);
|
|
}
|
|
|
|
function checkPrivateIdentifierPropertyAccess(leftType: Type, right: PrivateIdentifier, lexicallyScopedIdentifier: Symbol | undefined): boolean {
|
|
// Either the identifier could not be looked up in the lexical scope OR the lexically scoped identifier did not exist on the type.
|
|
// Find a private identifier with the same description on the type.
|
|
let propertyOnType: Symbol | undefined;
|
|
const properties = getPropertiesOfType(leftType);
|
|
if (properties) {
|
|
forEach(properties, (symbol: Symbol) => {
|
|
const decl = symbol.valueDeclaration;
|
|
if (decl && isNamedDeclaration(decl) && isPrivateIdentifier(decl.name) && decl.name.escapedText === right.escapedText) {
|
|
propertyOnType = symbol;
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
const diagName = diagnosticName(right);
|
|
if (propertyOnType) {
|
|
const typeValueDecl = propertyOnType.valueDeclaration;
|
|
const typeClass = getContainingClass(typeValueDecl);
|
|
Debug.assert(!!typeClass);
|
|
// We found a private identifier property with the same description.
|
|
// Either:
|
|
// - There is a lexically scoped private identifier AND it shadows the one we found on the type.
|
|
// - It is an attempt to access the private identifier outside of the class.
|
|
if (lexicallyScopedIdentifier) {
|
|
const lexicalValueDecl = lexicallyScopedIdentifier.valueDeclaration;
|
|
const lexicalClass = getContainingClass(lexicalValueDecl);
|
|
Debug.assert(!!lexicalClass);
|
|
if (findAncestor(lexicalClass, n => typeClass === n)) {
|
|
const diagnostic = error(
|
|
right,
|
|
Diagnostics.The_property_0_cannot_be_accessed_on_type_1_within_this_class_because_it_is_shadowed_by_another_private_identifier_with_the_same_spelling,
|
|
diagName,
|
|
typeToString(leftType)
|
|
);
|
|
|
|
addRelatedInfo(
|
|
diagnostic,
|
|
createDiagnosticForNode(
|
|
lexicalValueDecl,
|
|
Diagnostics.The_shadowing_declaration_of_0_is_defined_here,
|
|
diagName
|
|
),
|
|
createDiagnosticForNode(
|
|
typeValueDecl,
|
|
Diagnostics.The_declaration_of_0_that_you_probably_intended_to_use_is_defined_here,
|
|
diagName
|
|
)
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
error(
|
|
right,
|
|
Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier,
|
|
diagName,
|
|
diagnosticName(typeClass.name || anon)
|
|
);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier) {
|
|
const parentSymbol = getNodeLinks(left).resolvedSymbol;
|
|
const assignmentKind = getAssignmentTargetKind(node);
|
|
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType);
|
|
if (isPrivateIdentifier(right)) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet);
|
|
}
|
|
const isAnyLike = isTypeAny(apparentType) || apparentType === silentNeverType;
|
|
let prop: Symbol | undefined;
|
|
if (isPrivateIdentifier(right)) {
|
|
const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right);
|
|
if (isAnyLike) {
|
|
if (lexicallyScopedSymbol) {
|
|
return apparentType;
|
|
}
|
|
if (!getContainingClass(right)) {
|
|
grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
|
|
return anyType;
|
|
}
|
|
}
|
|
prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined;
|
|
// Check for private-identifier-specific shadowing and lexical-scoping errors.
|
|
if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) {
|
|
return errorType;
|
|
}
|
|
}
|
|
else {
|
|
if (isAnyLike) {
|
|
if (isIdentifier(left) && parentSymbol) {
|
|
markAliasReferenced(parentSymbol, node);
|
|
}
|
|
return apparentType;
|
|
}
|
|
prop = getPropertyOfType(apparentType, right.escapedText);
|
|
}
|
|
if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) {
|
|
markAliasReferenced(parentSymbol, node);
|
|
}
|
|
|
|
let propType: Type;
|
|
if (!prop) {
|
|
const indexInfo = !isPrivateIdentifier(right) && (assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType)) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined;
|
|
if (!(indexInfo && indexInfo.type)) {
|
|
if (isJSLiteralType(leftType)) {
|
|
return anyType;
|
|
}
|
|
if (leftType.symbol === globalThisSymbol) {
|
|
if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) {
|
|
error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType));
|
|
}
|
|
else if (noImplicitAny) {
|
|
error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType));
|
|
}
|
|
return anyType;
|
|
}
|
|
if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
|
|
reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType);
|
|
}
|
|
return errorType;
|
|
}
|
|
if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) {
|
|
error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType));
|
|
}
|
|
propType = indexInfo.type;
|
|
}
|
|
else {
|
|
checkPropertyNotUsedBeforeDeclaration(prop, node, right);
|
|
markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword);
|
|
getNodeLinks(node).resolvedSymbol = prop;
|
|
checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, apparentType, prop);
|
|
if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) {
|
|
error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right));
|
|
return errorType;
|
|
}
|
|
propType = getConstraintForLocation(getTypeOfSymbol(prop), node);
|
|
}
|
|
return getFlowTypeOfAccessExpression(node, prop, propType, right);
|
|
}
|
|
|
|
function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) {
|
|
// Only compute control flow type if this is a property access expression that isn't an
|
|
// assignment target, and the referenced property was declared as a variable, property,
|
|
// accessor, or optional method.
|
|
const assignmentKind = getAssignmentTargetKind(node);
|
|
if (!isAccessExpression(node) ||
|
|
assignmentKind === AssignmentKind.Definite ||
|
|
prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
|
|
return propType;
|
|
}
|
|
// If strict null checks and strict property initialization checks are enabled, if we have
|
|
// a this.xxx property access, if the property is an instance property without an initializer,
|
|
// and if we are in a constructor of the same class as the property declaration, assume that
|
|
// the property is uninitialized at the top of the control flow.
|
|
let assumeUninitialized = false;
|
|
if (strictNullChecks && strictPropertyInitialization && node.expression.kind === SyntaxKind.ThisKeyword) {
|
|
const declaration = prop && prop.valueDeclaration;
|
|
if (declaration && isInstancePropertyWithoutInitializer(declaration)) {
|
|
const flowContainer = getControlFlowContainer(node);
|
|
if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent && !(declaration.flags & NodeFlags.Ambient)) {
|
|
assumeUninitialized = true;
|
|
}
|
|
}
|
|
}
|
|
else if (strictNullChecks && prop && prop.valueDeclaration &&
|
|
isPropertyAccessExpression(prop.valueDeclaration) &&
|
|
getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) &&
|
|
getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) {
|
|
assumeUninitialized = true;
|
|
}
|
|
const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType);
|
|
if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
|
|
error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
|
|
// Return the declared type to reduce follow-on errors
|
|
return propType;
|
|
}
|
|
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
|
|
}
|
|
|
|
function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateIdentifier): void {
|
|
const { valueDeclaration } = prop;
|
|
if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) {
|
|
return;
|
|
}
|
|
|
|
let diagnosticMessage;
|
|
const declarationName = idText(right);
|
|
if (isInPropertyInitializer(node)
|
|
&& !(isAccessExpression(node) && isAccessExpression(node.expression))
|
|
&& !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)
|
|
&& !isPropertyDeclaredInAncestorClass(prop)) {
|
|
diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName);
|
|
}
|
|
else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration &&
|
|
node.parent.kind !== SyntaxKind.TypeReference &&
|
|
!(valueDeclaration.flags & NodeFlags.Ambient) &&
|
|
!isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) {
|
|
diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName);
|
|
}
|
|
|
|
if (diagnosticMessage) {
|
|
addRelatedInfo(diagnosticMessage,
|
|
createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName)
|
|
);
|
|
}
|
|
}
|
|
|
|
function isInPropertyInitializer(node: Node): boolean {
|
|
return !!findAncestor(node, node => {
|
|
switch (node.kind) {
|
|
case SyntaxKind.PropertyDeclaration:
|
|
return true;
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.SpreadAssignment:
|
|
case SyntaxKind.ComputedPropertyName:
|
|
case SyntaxKind.TemplateSpan:
|
|
case SyntaxKind.JsxExpression:
|
|
case SyntaxKind.JsxAttribute:
|
|
case SyntaxKind.JsxAttributes:
|
|
case SyntaxKind.JsxSpreadAttribute:
|
|
case SyntaxKind.JsxOpeningElement:
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
case SyntaxKind.HeritageClause:
|
|
return false;
|
|
default:
|
|
return isExpressionNode(node) ? false : "quit";
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass.
|
|
* In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration.
|
|
*/
|
|
function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean {
|
|
if (!(prop.parent!.flags & SymbolFlags.Class)) {
|
|
return false;
|
|
}
|
|
let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType;
|
|
while (true) {
|
|
classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined;
|
|
if (!classType) {
|
|
return false;
|
|
}
|
|
const superProperty = getPropertyOfType(classType, prop.escapedName);
|
|
if (superProperty && superProperty.valueDeclaration) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getSuperClass(classType: InterfaceType): Type | undefined {
|
|
const x = getBaseTypes(classType);
|
|
if (x.length === 0) {
|
|
return undefined;
|
|
}
|
|
return getIntersectionType(x);
|
|
}
|
|
|
|
function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type) {
|
|
let errorInfo: DiagnosticMessageChain | undefined;
|
|
let relatedInfo: Diagnostic | undefined;
|
|
if (!isPrivateIdentifier(propNode) && containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
|
|
for (const subtype of (containingType as UnionType).types) {
|
|
if (!getPropertyOfType(subtype, propNode.escapedText) && !getIndexInfoOfType(subtype, IndexKind.String)) {
|
|
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (typeHasStaticProperty(propNode.escapedText, containingType)) {
|
|
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_is_a_static_member_of_type_1, declarationNameToString(propNode), typeToString(containingType));
|
|
}
|
|
else {
|
|
const promisedType = getPromisedTypeOfPromise(containingType);
|
|
if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) {
|
|
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
|
|
relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await);
|
|
}
|
|
else {
|
|
const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType);
|
|
if (suggestion !== undefined) {
|
|
const suggestedName = symbolName(suggestion);
|
|
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestedName);
|
|
relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName);
|
|
}
|
|
else {
|
|
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
|
|
}
|
|
}
|
|
}
|
|
const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo);
|
|
if (relatedInfo) {
|
|
addRelatedInfo(resultDiagnostic, relatedInfo);
|
|
}
|
|
diagnostics.add(resultDiagnostic);
|
|
}
|
|
|
|
function typeHasStaticProperty(propName: __String, containingType: Type): boolean {
|
|
const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName);
|
|
return prop !== undefined && prop.valueDeclaration && hasModifier(prop.valueDeclaration, ModifierFlags.Static);
|
|
}
|
|
|
|
function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
|
|
return getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value);
|
|
}
|
|
|
|
function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined {
|
|
const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType);
|
|
return suggestion && symbolName(suggestion);
|
|
}
|
|
|
|
function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined {
|
|
Debug.assert(outerName !== undefined, "outername should always be defined");
|
|
const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => {
|
|
Debug.assertEqual(outerName, name, "name should equal outerName");
|
|
const symbol = getSymbol(symbols, name, meaning);
|
|
// Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function
|
|
// So the table *contains* `x` but `x` isn't actually in scope.
|
|
// However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion.
|
|
return symbol || getSpellingSuggestionForName(unescapeLeadingUnderscores(name), arrayFrom(symbols.values()), meaning);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined {
|
|
const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning);
|
|
return symbolResult && symbolName(symbolResult);
|
|
}
|
|
|
|
function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined {
|
|
return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember);
|
|
}
|
|
|
|
function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined {
|
|
const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule);
|
|
return suggestion && symbolName(suggestion);
|
|
}
|
|
|
|
function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined {
|
|
// check if object type has setter or getter
|
|
function hasProp(name: "set" | "get") {
|
|
const prop = getPropertyOfObjectType(objectType, <__String>name);
|
|
if (prop) {
|
|
const s = getSingleCallSignature(getTypeOfSymbol(prop));
|
|
return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0));
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get";
|
|
if (!hasProp(suggestedMethod)) {
|
|
return undefined;
|
|
}
|
|
|
|
let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression);
|
|
if (suggestion === undefined) {
|
|
suggestion = suggestedMethod;
|
|
}
|
|
else {
|
|
suggestion += "." + suggestedMethod;
|
|
}
|
|
|
|
return suggestion;
|
|
}
|
|
|
|
/**
|
|
* Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
|
|
* Names less than length 3 only check for case-insensitive equality, not levenshtein distance.
|
|
*
|
|
* If there is a candidate that's the same except for case, return that.
|
|
* If there is a candidate that's within one edit of the name, return that.
|
|
* Otherwise, return the candidate with the smallest Levenshtein distance,
|
|
* except for candidates:
|
|
* * With no name
|
|
* * Whose meaning doesn't match the `meaning` parameter.
|
|
* * Whose length differs from the target name by more than 0.34 of the length of the name.
|
|
* * Whose levenshtein distance is more than 0.4 of the length of the name
|
|
* (0.4 allows 1 substitution/transposition for every 5 characters,
|
|
* and 1 insertion/deletion at 3 characters)
|
|
*/
|
|
function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined {
|
|
return getSpellingSuggestion(name, symbols, getCandidateName);
|
|
function getCandidateName(candidate: Symbol) {
|
|
const candidateName = symbolName(candidate);
|
|
if (startsWith(candidateName, "\"")) {
|
|
return undefined;
|
|
}
|
|
|
|
if (candidate.flags & meaning) {
|
|
return candidateName;
|
|
}
|
|
|
|
if (candidate.flags & SymbolFlags.Alias) {
|
|
const alias = tryResolveAlias(candidate);
|
|
if (alias && alias.flags & meaning) {
|
|
return candidateName;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isThisAccess: boolean) {
|
|
const valueDeclaration = prop && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration;
|
|
if (!valueDeclaration) {
|
|
return;
|
|
}
|
|
const hasPrivateModifier = hasModifier(valueDeclaration, ModifierFlags.Private);
|
|
const hasPrivateIdentifier = isNamedDeclaration(prop.valueDeclaration) && isPrivateIdentifier(prop.valueDeclaration.name);
|
|
if (!hasPrivateModifier && !hasPrivateIdentifier) {
|
|
return;
|
|
}
|
|
if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor))) {
|
|
return;
|
|
}
|
|
|
|
if (isThisAccess) {
|
|
// Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters).
|
|
const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration);
|
|
if (containingMethod && containingMethod.symbol === prop) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
(getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All;
|
|
}
|
|
|
|
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression)));
|
|
case SyntaxKind.QualifiedName:
|
|
return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left)));
|
|
case SyntaxKind.ImportType:
|
|
return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node));
|
|
}
|
|
}
|
|
|
|
function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean {
|
|
return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type);
|
|
// Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context.
|
|
}
|
|
|
|
function isValidPropertyAccessWithType(
|
|
node: PropertyAccessExpression | QualifiedName | ImportTypeNode,
|
|
isSuper: boolean,
|
|
propertyName: __String,
|
|
type: Type): boolean {
|
|
|
|
if (type === errorType || isTypeAny(type)) {
|
|
return true;
|
|
}
|
|
const prop = getPropertyOfType(type, propertyName);
|
|
if (prop) {
|
|
if (isPropertyAccessExpression(node) && prop.valueDeclaration && isPrivateIdentifierPropertyDeclaration(prop.valueDeclaration)) {
|
|
const declClass = getContainingClass(prop.valueDeclaration);
|
|
return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass);
|
|
}
|
|
return checkPropertyAccessibility(node, isSuper, type, prop);
|
|
}
|
|
// In js files properties of unions are allowed in completion
|
|
return isInJSFile(node) && (type.flags & TypeFlags.Union) !== 0 && (<UnionType>type).types.some(elementType => isValidPropertyAccessWithType(node, isSuper, propertyName, elementType));
|
|
}
|
|
|
|
/**
|
|
* Return the symbol of the for-in variable declared or referenced by the given for-in statement.
|
|
*/
|
|
function getForInVariableSymbol(node: ForInStatement): Symbol | undefined {
|
|
const initializer = node.initializer;
|
|
if (initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
const variable = (<VariableDeclarationList>initializer).declarations[0];
|
|
if (variable && !isBindingPattern(variable.name)) {
|
|
return getSymbolOfNode(variable);
|
|
}
|
|
}
|
|
else if (initializer.kind === SyntaxKind.Identifier) {
|
|
return getResolvedSymbol(<Identifier>initializer);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Return true if the given type is considered to have numeric property names.
|
|
*/
|
|
function hasNumericPropertyNames(type: Type) {
|
|
return getIndexTypeOfType(type, IndexKind.Number) && !getIndexTypeOfType(type, IndexKind.String);
|
|
}
|
|
|
|
/**
|
|
* Return true if given node is an expression consisting of an identifier (possibly parenthesized)
|
|
* that references a for-in variable for an object with numeric property names.
|
|
*/
|
|
function isForInVariableForNumericPropertyNames(expr: Expression) {
|
|
const e = skipParentheses(expr);
|
|
if (e.kind === SyntaxKind.Identifier) {
|
|
const symbol = getResolvedSymbol(<Identifier>e);
|
|
if (symbol.flags & SymbolFlags.Variable) {
|
|
let child: Node = expr;
|
|
let node = expr.parent;
|
|
while (node) {
|
|
if (node.kind === SyntaxKind.ForInStatement &&
|
|
child === (<ForInStatement>node).statement &&
|
|
getForInVariableSymbol(<ForInStatement>node) === symbol &&
|
|
hasNumericPropertyNames(getTypeOfExpression((<ForInStatement>node).expression))) {
|
|
return true;
|
|
}
|
|
child = node;
|
|
node = node.parent;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkIndexedAccess(node: ElementAccessExpression): Type {
|
|
return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain) :
|
|
checkElementAccessExpression(node, checkNonNullExpression(node.expression));
|
|
}
|
|
|
|
function checkElementAccessChain(node: ElementAccessChain) {
|
|
const exprType = checkExpression(node.expression);
|
|
const nonOptionalType = getOptionalExpressionType(exprType, node.expression);
|
|
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType);
|
|
}
|
|
|
|
function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type {
|
|
const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType;
|
|
const indexExpression = node.argumentExpression;
|
|
const indexType = checkExpression(indexExpression);
|
|
|
|
if (objectType === errorType || objectType === silentNeverType) {
|
|
return objectType;
|
|
}
|
|
|
|
if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) {
|
|
error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
|
|
return errorType;
|
|
}
|
|
|
|
const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType;
|
|
const accessFlags = isAssignmentTarget(node) ?
|
|
AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) :
|
|
AccessFlags.None;
|
|
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType;
|
|
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node);
|
|
}
|
|
|
|
function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
|
|
if (expressionType === errorType) {
|
|
// There is already an error, so no need to report one.
|
|
return false;
|
|
}
|
|
|
|
if (!isWellKnownSymbolSyntactically(expression)) {
|
|
return false;
|
|
}
|
|
|
|
// Make sure the property type is the primitive symbol type
|
|
if ((expressionType.flags & TypeFlags.ESSymbolLike) === 0) {
|
|
if (reportError) {
|
|
error(expression, Diagnostics.A_computed_property_name_of_the_form_0_must_be_of_type_symbol, getTextOfNode(expression));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// The name is Symbol.<someName>, so make sure Symbol actually resolves to the
|
|
// global Symbol object
|
|
const leftHandSide = <Identifier>(<PropertyAccessExpression>expression).expression;
|
|
const leftHandSideSymbol = getResolvedSymbol(leftHandSide);
|
|
if (!leftHandSideSymbol) {
|
|
return false;
|
|
}
|
|
|
|
const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ true);
|
|
if (!globalESSymbol) {
|
|
// Already errored when we tried to look up the symbol
|
|
return false;
|
|
}
|
|
|
|
if (leftHandSideSymbol !== globalESSymbol) {
|
|
if (reportError) {
|
|
error(leftHandSide, Diagnostics.Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement {
|
|
return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node);
|
|
}
|
|
|
|
function resolveUntypedCall(node: CallLikeExpression): Signature {
|
|
if (callLikeExpressionMayHaveTypeArguments(node)) {
|
|
// Check type arguments even though we will give an error that untyped calls may not accept type arguments.
|
|
// This gets us diagnostics for the type arguments and marks them as referenced.
|
|
forEach(node.typeArguments, checkSourceElement);
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
|
|
checkExpression(node.template);
|
|
}
|
|
else if (isJsxOpeningLikeElement(node)) {
|
|
checkExpression(node.attributes);
|
|
}
|
|
else if (node.kind !== SyntaxKind.Decorator) {
|
|
forEach((<CallExpression>node).arguments, argument => {
|
|
checkExpression(argument);
|
|
});
|
|
}
|
|
return anySignature;
|
|
}
|
|
|
|
function resolveErrorCall(node: CallLikeExpression): Signature {
|
|
resolveUntypedCall(node);
|
|
return unknownSignature;
|
|
}
|
|
|
|
// Re-order candidate signatures into the result array. Assumes the result array to be empty.
|
|
// The candidate list orders groups in reverse, but within a group signatures are kept in declaration order
|
|
// A nit here is that we reorder only signatures that belong to the same symbol,
|
|
// so order how inherited signatures are processed is still preserved.
|
|
// interface A { (x: string): void }
|
|
// interface B extends A { (x: 'foo'): string }
|
|
// const b: B;
|
|
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
|
|
function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void {
|
|
let lastParent: Node | undefined;
|
|
let lastSymbol: Symbol | undefined;
|
|
let cutoffIndex = 0;
|
|
let index: number | undefined;
|
|
let specializedIndex = -1;
|
|
let spliceIndex: number;
|
|
Debug.assert(!result.length);
|
|
for (const signature of signatures) {
|
|
const symbol = signature.declaration && getSymbolOfNode(signature.declaration);
|
|
const parent = signature.declaration && signature.declaration.parent;
|
|
if (!lastSymbol || symbol === lastSymbol) {
|
|
if (lastParent && parent === lastParent) {
|
|
index = index! + 1;
|
|
}
|
|
else {
|
|
lastParent = parent;
|
|
index = cutoffIndex;
|
|
}
|
|
}
|
|
else {
|
|
// current declaration belongs to a different symbol
|
|
// set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex
|
|
index = cutoffIndex = result.length;
|
|
lastParent = parent;
|
|
}
|
|
lastSymbol = symbol;
|
|
|
|
// specialized signatures always need to be placed before non-specialized signatures regardless
|
|
// of the cutoff position; see GH#1133
|
|
if (signatureHasLiteralTypes(signature)) {
|
|
specializedIndex++;
|
|
spliceIndex = specializedIndex;
|
|
// The cutoff index always needs to be greater than or equal to the specialized signature index
|
|
// in order to prevent non-specialized signatures from being added before a specialized
|
|
// signature.
|
|
cutoffIndex++;
|
|
}
|
|
else {
|
|
spliceIndex = index;
|
|
}
|
|
|
|
result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature);
|
|
}
|
|
}
|
|
|
|
function isSpreadArgument(arg: Expression | undefined): arg is Expression {
|
|
return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (<SyntheticExpression>arg).isSpread);
|
|
}
|
|
|
|
function getSpreadArgumentIndex(args: readonly Expression[]): number {
|
|
return findIndex(args, isSpreadArgument);
|
|
}
|
|
|
|
function acceptsVoid(t: Type): boolean {
|
|
return !!(t.flags & TypeFlags.Void);
|
|
}
|
|
|
|
function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) {
|
|
let argCount: number;
|
|
let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments
|
|
let effectiveParameterCount = getParameterCount(signature);
|
|
let effectiveMinimumArguments = getMinArgumentCount(signature);
|
|
|
|
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
|
|
argCount = args.length;
|
|
if (node.template.kind === SyntaxKind.TemplateExpression) {
|
|
// If a tagged template expression lacks a tail literal, the call is incomplete.
|
|
// Specifically, a template only can end in a TemplateTail or a Missing literal.
|
|
const lastSpan = last(node.template.templateSpans); // we should always have at least one span.
|
|
callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated;
|
|
}
|
|
else {
|
|
// If the template didn't end in a backtick, or its beginning occurred right prior to EOF,
|
|
// then this might actually turn out to be a TemplateHead in the future;
|
|
// so we consider the call to be incomplete.
|
|
const templateLiteral = <LiteralExpression>node.template;
|
|
Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral);
|
|
callIsIncomplete = !!templateLiteral.isUnterminated;
|
|
}
|
|
}
|
|
else if (node.kind === SyntaxKind.Decorator) {
|
|
argCount = getDecoratorArgumentCount(node, signature);
|
|
}
|
|
else if (isJsxOpeningLikeElement(node)) {
|
|
callIsIncomplete = node.attributes.end === node.end;
|
|
if (callIsIncomplete) {
|
|
return true;
|
|
}
|
|
argCount = effectiveMinimumArguments === 0 ? args.length : 1;
|
|
effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type
|
|
effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked
|
|
}
|
|
else {
|
|
if (!node.arguments) {
|
|
// This only happens when we have something of the form: 'new C'
|
|
Debug.assert(node.kind === SyntaxKind.NewExpression);
|
|
return getMinArgumentCount(signature) === 0;
|
|
}
|
|
|
|
argCount = signatureHelpTrailingComma ? args.length + 1 : args.length;
|
|
|
|
// If we are missing the close parenthesis, the call is incomplete.
|
|
callIsIncomplete = node.arguments.end === node.end;
|
|
|
|
// If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range.
|
|
const spreadArgIndex = getSpreadArgumentIndex(args);
|
|
if (spreadArgIndex >= 0) {
|
|
return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature));
|
|
}
|
|
}
|
|
|
|
// Too many arguments implies incorrect arity.
|
|
if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) {
|
|
return false;
|
|
}
|
|
|
|
// If the call is incomplete, we should skip the lower bound check.
|
|
// JSX signatures can have extra parameters provided by the library which we don't check
|
|
if (callIsIncomplete || argCount >= effectiveMinimumArguments) {
|
|
return true;
|
|
}
|
|
for (let i = argCount; i < effectiveMinimumArguments; i++) {
|
|
const type = getTypeAtPosition(signature, i);
|
|
if (filterType(type, acceptsVoid).flags & TypeFlags.Never) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray<TypeNode> | undefined) {
|
|
// If the user supplied type arguments, but the number of type arguments does not match
|
|
// the declared number of type parameters, the call has an incorrect arity.
|
|
const numTypeParameters = length(signature.typeParameters);
|
|
const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters);
|
|
return !some(typeArguments) ||
|
|
(typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters);
|
|
}
|
|
|
|
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
|
|
function getSingleCallSignature(type: Type): Signature | undefined {
|
|
return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false);
|
|
}
|
|
|
|
function getSingleCallOrConstructSignature(type: Type): Signature | undefined {
|
|
return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) ||
|
|
getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false);
|
|
}
|
|
|
|
function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined {
|
|
if (type.flags & TypeFlags.Object) {
|
|
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
|
|
if (allowMembers || resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) {
|
|
if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) {
|
|
return resolved.callSignatures[0];
|
|
}
|
|
if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) {
|
|
return resolved.constructSignatures[0];
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec)
|
|
function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature {
|
|
const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes);
|
|
// We clone the inferenceContext to avoid fixing. For example, when the source signature is <T>(x: T) => T[] and
|
|
// the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any')
|
|
// for T but leave it possible to later infer '[any]' back to A.
|
|
const restType = getEffectiveRestType(contextualSignature);
|
|
const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper);
|
|
const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature;
|
|
applyToParameterTypes(sourceSignature, signature, (source, target) => {
|
|
// Type parameters from outer context referenced by source type are fixed by instantiation of the source type
|
|
inferTypes(context.inferences, source, target);
|
|
});
|
|
if (!inferenceContext) {
|
|
applyToReturnTypes(contextualSignature, signature, (source, target) => {
|
|
inferTypes(context.inferences, source, target, InferencePriority.ReturnType);
|
|
});
|
|
}
|
|
return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration));
|
|
}
|
|
|
|
function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] {
|
|
const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node);
|
|
const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode);
|
|
inferTypes(context.inferences, checkAttrType, paramType);
|
|
return getInferredTypes(context);
|
|
}
|
|
|
|
function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] {
|
|
if (isJsxOpeningLikeElement(node)) {
|
|
return inferJsxTypeArguments(node, signature, checkMode, context);
|
|
}
|
|
|
|
// If a contextual type is available, infer from that type to the return type of the call expression. For
|
|
// example, given a 'function wrap<T, U>(cb: (x: T) => U): (x: T) => U' and a call expression
|
|
// 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the
|
|
// return type of 'wrap'.
|
|
if (node.kind !== SyntaxKind.Decorator) {
|
|
const contextualType = getContextualType(node);
|
|
if (contextualType) {
|
|
// We clone the inference context to avoid disturbing a resolution in progress for an
|
|
// outer call expression. Effectively we just want a snapshot of whatever has been
|
|
// inferred for any outer call expression so far.
|
|
const outerContext = getInferenceContext(node);
|
|
const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault));
|
|
const instantiatedType = instantiateType(contextualType, outerMapper);
|
|
// If the contextual type is a generic function type with a single call signature, we
|
|
// instantiate the type with its own type parameters and type arguments. This ensures that
|
|
// the type parameters are not erased to type any during type inference such that they can
|
|
// be inferred as actual types from the contextual type. For example:
|
|
// declare function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[];
|
|
// const boxElements: <A>(a: A[]) => { value: A }[] = arrayMap(value => ({ value }));
|
|
// Above, the type of the 'value' parameter is inferred to be 'A'.
|
|
const contextualSignature = getSingleCallSignature(instantiatedType);
|
|
const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ?
|
|
getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) :
|
|
instantiatedType;
|
|
const inferenceTargetType = getReturnTypeOfSignature(signature);
|
|
// Inferences made from return types have lower priority than all other inferences.
|
|
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
|
|
// Create a type mapper for instantiating generic contextual types using the inferences made
|
|
// from the return type. We need a separate inference pass here because (a) instantiation of
|
|
// the source type uses the outer context's return mapper (which excludes inferences made from
|
|
// outer arguments), and (b) we don't want any further inferences going into this context.
|
|
const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags);
|
|
const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper);
|
|
inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType);
|
|
context.returnMapper = some(returnContext.inferences, hasInferenceCandidates) ? getMapperFromContext(cloneInferredPartOfContext(returnContext)) : undefined;
|
|
}
|
|
}
|
|
|
|
const thisType = getThisTypeOfSignature(signature);
|
|
if (thisType) {
|
|
const thisArgumentNode = getThisArgumentOfCall(node);
|
|
const thisArgumentType = thisArgumentNode ? checkExpression(thisArgumentNode) : voidType;
|
|
inferTypes(context.inferences, thisArgumentType, thisType);
|
|
}
|
|
|
|
const restType = getNonArrayRestType(signature);
|
|
const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length;
|
|
for (let i = 0; i < argCount; i++) {
|
|
const arg = args[i];
|
|
if (arg.kind !== SyntaxKind.OmittedExpression) {
|
|
const paramType = getTypeAtPosition(signature, i);
|
|
const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode);
|
|
inferTypes(context.inferences, argType, paramType);
|
|
}
|
|
}
|
|
|
|
if (restType) {
|
|
const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, context);
|
|
inferTypes(context.inferences, spreadType, restType);
|
|
}
|
|
|
|
return getInferredTypes(context);
|
|
}
|
|
|
|
function getArrayifiedType(type: Type) {
|
|
return type.flags & TypeFlags.Union ? mapType(type, getArrayifiedType) :
|
|
type.flags & (TypeFlags.Any | TypeFlags.Instantiable) || isMutableArrayOrTuple(type) ? type :
|
|
isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.minLength, type.target.hasRestElement, /*readonly*/ false, type.target.associatedNames) :
|
|
createArrayType(getIndexedAccessType(type, numberType));
|
|
}
|
|
|
|
function getSpreadArgumentType(args: readonly Expression[], index: number, argCount: number, restType: Type, context: InferenceContext | undefined) {
|
|
if (index >= argCount - 1) {
|
|
const arg = args[argCount - 1];
|
|
if (isSpreadArgument(arg)) {
|
|
// We are inferring from a spread expression in the last argument position, i.e. both the parameter
|
|
// and the argument are ...x forms.
|
|
return arg.kind === SyntaxKind.SyntheticExpression ?
|
|
createArrayType((<SyntheticExpression>arg).type) :
|
|
getArrayifiedType(checkExpressionWithContextualType((<SpreadElement>arg).expression, restType, context, CheckMode.Normal));
|
|
}
|
|
}
|
|
const types = [];
|
|
let spreadIndex = -1;
|
|
for (let i = index; i < argCount; i++) {
|
|
const contextualType = getIndexedAccessType(restType, getLiteralType(i - index));
|
|
const argType = checkExpressionWithContextualType(args[i], contextualType, context, CheckMode.Normal);
|
|
if (spreadIndex < 0 && isSpreadArgument(args[i])) {
|
|
spreadIndex = i - index;
|
|
}
|
|
const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index);
|
|
types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType));
|
|
}
|
|
return spreadIndex < 0 ?
|
|
createTupleType(types) :
|
|
createTupleType(append(types.slice(0, spreadIndex), getUnionType(types.slice(spreadIndex))), spreadIndex, /*hasRestElement*/ true);
|
|
}
|
|
|
|
function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined {
|
|
const isJavascript = isInJSFile(signature.declaration);
|
|
const typeParameters = signature.typeParameters!;
|
|
const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript);
|
|
let mapper: TypeMapper | undefined;
|
|
for (let i = 0; i < typeArgumentNodes.length; i++) {
|
|
Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments");
|
|
const constraint = getConstraintOfTypeParameter(typeParameters[i]);
|
|
if (constraint) {
|
|
const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined;
|
|
const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1;
|
|
if (!mapper) {
|
|
mapper = createTypeMapper(typeParameters, typeArgumentTypes);
|
|
}
|
|
const typeArgument = typeArgumentTypes[i];
|
|
if (!checkTypeAssignableTo(
|
|
typeArgument,
|
|
getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument),
|
|
reportErrors ? typeArgumentNodes[i] : undefined,
|
|
typeArgumentHeadMessage,
|
|
errorInfo)) {
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|
|
return typeArgumentTypes;
|
|
}
|
|
|
|
function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind {
|
|
if (isJsxIntrinsicIdentifier(node.tagName)) {
|
|
return JsxReferenceKind.Mixed;
|
|
}
|
|
const tagType = getApparentType(checkExpression(node.tagName));
|
|
if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) {
|
|
return JsxReferenceKind.Component;
|
|
}
|
|
if (length(getSignaturesOfType(tagType, SignatureKind.Call))) {
|
|
return JsxReferenceKind.Function;
|
|
}
|
|
return JsxReferenceKind.Mixed;
|
|
}
|
|
|
|
/**
|
|
* Check if the given signature can possibly be a signature called by the JSX opening-like element.
|
|
* @param node a JSX opening-like element we are trying to figure its call signature
|
|
* @param signature a candidate signature we are trying whether it is a call signature
|
|
* @param relation a relationship to check parameter and argument type
|
|
*/
|
|
function checkApplicableSignatureForJsxOpeningLikeElement(
|
|
node: JsxOpeningLikeElement,
|
|
signature: Signature,
|
|
relation: Map<RelationComparisonResult>,
|
|
checkMode: CheckMode,
|
|
reportErrors: boolean,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean }
|
|
) {
|
|
// Stateless function components can have maximum of three arguments: "props", "context", and "updater".
|
|
// However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props,
|
|
// can be specified by users through attributes property.
|
|
const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node);
|
|
const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode);
|
|
return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate(
|
|
attributesType,
|
|
paramType,
|
|
relation,
|
|
reportErrors ? node.tagName : undefined,
|
|
node.attributes,
|
|
/*headMessage*/ undefined,
|
|
containingMessageChain,
|
|
errorOutputContainer);
|
|
|
|
function checkTagNameDoesNotExpectTooManyArguments(): boolean {
|
|
const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined;
|
|
if (!tagType) {
|
|
return true;
|
|
}
|
|
const tagCallSignatures = getSignaturesOfType(tagType, SignatureKind.Call);
|
|
if (!length(tagCallSignatures)) {
|
|
return true;
|
|
}
|
|
const factory = getJsxFactoryEntity(node);
|
|
if (!factory) {
|
|
return true;
|
|
}
|
|
const factorySymbol = resolveEntityName(factory, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, node);
|
|
if (!factorySymbol) {
|
|
return true;
|
|
}
|
|
|
|
const factoryType = getTypeOfSymbol(factorySymbol);
|
|
const callSignatures = getSignaturesOfType(factoryType, SignatureKind.Call);
|
|
if (!length(callSignatures)) {
|
|
return true;
|
|
}
|
|
|
|
let hasFirstParamSignatures = false;
|
|
let maxParamCount = 0;
|
|
// Check that _some_ first parameter expects a FC-like thing, and that some overload of the SFC expects an acceptable number of arguments
|
|
for (const sig of callSignatures) {
|
|
const firstparam = getTypeAtPosition(sig, 0);
|
|
const signaturesOfParam = getSignaturesOfType(firstparam, SignatureKind.Call);
|
|
if (!length(signaturesOfParam)) continue;
|
|
for (const paramSig of signaturesOfParam) {
|
|
hasFirstParamSignatures = true;
|
|
if (hasEffectiveRestParameter(paramSig)) {
|
|
return true; // some signature has a rest param, so function components can have an arbitrary number of arguments
|
|
}
|
|
const paramCount = getParameterCount(paramSig);
|
|
if (paramCount > maxParamCount) {
|
|
maxParamCount = paramCount;
|
|
}
|
|
}
|
|
}
|
|
if (!hasFirstParamSignatures) {
|
|
// Not a single signature had a first parameter which expected a signature - for back compat, and
|
|
// to guard against generic factories which won't have signatures directly, do not error
|
|
return true;
|
|
}
|
|
let absoluteMinArgCount = Infinity;
|
|
for (const tagSig of tagCallSignatures) {
|
|
const tagRequiredArgCount = getMinArgumentCount(tagSig);
|
|
if (tagRequiredArgCount < absoluteMinArgCount) {
|
|
absoluteMinArgCount = tagRequiredArgCount;
|
|
}
|
|
}
|
|
if (absoluteMinArgCount <= maxParamCount) {
|
|
return true; // some signature accepts the number of arguments the function component provides
|
|
}
|
|
|
|
if (reportErrors) {
|
|
const diag = createDiagnosticForNode(node.tagName, Diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(node.tagName), absoluteMinArgCount, entityNameToString(factory), maxParamCount);
|
|
const tagNameDeclaration = getSymbolAtLocation(node.tagName)?.valueDeclaration;
|
|
if (tagNameDeclaration) {
|
|
addRelatedInfo(diag, createDiagnosticForNode(tagNameDeclaration, Diagnostics._0_is_declared_here, entityNameToString(node.tagName)));
|
|
}
|
|
if (errorOutputContainer && errorOutputContainer.skipLogging) {
|
|
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
|
|
}
|
|
if (!errorOutputContainer.skipLogging) {
|
|
diagnostics.add(diag);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getSignatureApplicabilityError(
|
|
node: CallLikeExpression,
|
|
args: readonly Expression[],
|
|
signature: Signature,
|
|
relation: Map<RelationComparisonResult>,
|
|
checkMode: CheckMode,
|
|
reportErrors: boolean,
|
|
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
|
|
): readonly Diagnostic[] | undefined {
|
|
|
|
const errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } = { errors: undefined, skipLogging: true };
|
|
if (isJsxOpeningLikeElement(node)) {
|
|
if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) {
|
|
Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors");
|
|
return errorOutputContainer.errors || emptyArray;
|
|
}
|
|
return undefined;
|
|
}
|
|
const thisType = getThisTypeOfSignature(signature);
|
|
if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) {
|
|
// If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType
|
|
// If the signature's 'this' type is voidType, then the check is skipped -- anything is compatible.
|
|
// If the expression is a new expression, then the check is skipped.
|
|
const thisArgumentNode = getThisArgumentOfCall(node);
|
|
let thisArgumentType: Type;
|
|
if (thisArgumentNode) {
|
|
thisArgumentType = checkExpression(thisArgumentNode);
|
|
if (isOptionalChainRoot(thisArgumentNode.parent)) {
|
|
thisArgumentType = getNonNullableType(thisArgumentType);
|
|
}
|
|
else if (isOptionalChain(thisArgumentNode.parent)) {
|
|
thisArgumentType = removeOptionalTypeMarker(thisArgumentType);
|
|
}
|
|
}
|
|
else {
|
|
thisArgumentType = voidType;
|
|
}
|
|
|
|
const errorNode = reportErrors ? (thisArgumentNode || node) : undefined;
|
|
const headMessage = Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1;
|
|
if (!checkTypeRelatedTo(thisArgumentType, thisType, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer)) {
|
|
Debug.assert(!reportErrors || !!errorOutputContainer.errors, "this parameter should have errors when reporting errors");
|
|
return errorOutputContainer.errors || emptyArray;
|
|
}
|
|
}
|
|
const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1;
|
|
const restType = getNonArrayRestType(signature);
|
|
const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length;
|
|
for (let i = 0; i < argCount; i++) {
|
|
const arg = args[i];
|
|
if (arg.kind !== SyntaxKind.OmittedExpression) {
|
|
const paramType = getTypeAtPosition(signature, i);
|
|
const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode);
|
|
// If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive),
|
|
// we obtain the regular type of any object literal arguments because we may not have inferred complete
|
|
// parameter types yet and therefore excess property checks may yield false positives (see #17041).
|
|
const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType;
|
|
if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage, containingMessageChain, errorOutputContainer)) {
|
|
Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors");
|
|
maybeAddMissingAwaitInfo(arg, checkArgType, paramType);
|
|
return errorOutputContainer.errors || emptyArray;
|
|
}
|
|
}
|
|
}
|
|
if (restType) {
|
|
const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, /*context*/ undefined);
|
|
const errorNode = reportErrors ? argCount < args.length ? args[argCount] : node : undefined;
|
|
if (!checkTypeRelatedTo(spreadType, restType, relation, errorNode, headMessage, /*containingMessageChain*/ undefined, errorOutputContainer)) {
|
|
Debug.assert(!reportErrors || !!errorOutputContainer.errors, "rest parameter should have errors when reporting errors");
|
|
maybeAddMissingAwaitInfo(errorNode, spreadType, restType);
|
|
return errorOutputContainer.errors || emptyArray;
|
|
}
|
|
}
|
|
return undefined;
|
|
|
|
function maybeAddMissingAwaitInfo(errorNode: Node | undefined, source: Type, target: Type) {
|
|
if (errorNode && reportErrors && errorOutputContainer.errors && errorOutputContainer.errors.length) {
|
|
// Bail if target is Promise-like---something else is wrong
|
|
if (getAwaitedTypeOfPromise(target)) {
|
|
return;
|
|
}
|
|
const awaitedTypeOfSource = getAwaitedTypeOfPromise(source);
|
|
if (awaitedTypeOfSource && isTypeRelatedTo(awaitedTypeOfSource, target, relation)) {
|
|
addRelatedInfo(errorOutputContainer.errors[0], createDiagnosticForNode(errorNode, Diagnostics.Did_you_forget_to_use_await));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise.
|
|
*/
|
|
function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression | undefined {
|
|
if (node.kind === SyntaxKind.CallExpression) {
|
|
const callee = skipOuterExpressions(node.expression);
|
|
if (isAccessExpression(callee)) {
|
|
return callee.expression;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean) {
|
|
const result = <SyntheticExpression>createNode(SyntaxKind.SyntheticExpression, parent.pos, parent.end);
|
|
result.parent = parent;
|
|
result.type = type;
|
|
result.isSpread = isSpread || false;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns the effective arguments for an expression that works like a function invocation.
|
|
*/
|
|
function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] {
|
|
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
|
|
const template = node.template;
|
|
const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())];
|
|
if (template.kind === SyntaxKind.TemplateExpression) {
|
|
forEach(template.templateSpans, span => {
|
|
args.push(span.expression);
|
|
});
|
|
}
|
|
return args;
|
|
}
|
|
if (node.kind === SyntaxKind.Decorator) {
|
|
return getEffectiveDecoratorArguments(node);
|
|
}
|
|
if (isJsxOpeningLikeElement(node)) {
|
|
return node.attributes.properties.length > 0 || (isJsxOpeningElement(node) && node.parent.children.length > 0) ? [node.attributes] : emptyArray;
|
|
}
|
|
const args = node.arguments || emptyArray;
|
|
const length = args.length;
|
|
if (length && isSpreadArgument(args[length - 1]) && getSpreadArgumentIndex(args) === length - 1) {
|
|
// We have a spread argument in the last position and no other spread arguments. If the type
|
|
// of the argument is a tuple type, spread the tuple elements into the argument list. We can
|
|
// call checkExpressionCached because spread expressions never have a contextual type.
|
|
const spreadArgument = <SpreadElement>args[length - 1];
|
|
const type = flowLoopCount ? checkExpression(spreadArgument.expression) : checkExpressionCached(spreadArgument.expression);
|
|
if (isTupleType(type)) {
|
|
const typeArguments = getTypeArguments(<TypeReference>type);
|
|
const restIndex = type.target.hasRestElement ? typeArguments.length - 1 : -1;
|
|
const syntheticArgs = map(typeArguments, (t, i) => createSyntheticExpression(spreadArgument, t, /*isSpread*/ i === restIndex));
|
|
return concatenate(args.slice(0, length - 1), syntheticArgs);
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
|
|
/**
|
|
* Returns the synthetic argument list for a decorator invocation.
|
|
*/
|
|
function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] {
|
|
const parent = node.parent;
|
|
const expr = node.expression;
|
|
switch (parent.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
// For a class decorator, the `target` is the type of the class (e.g. the
|
|
// "static" or "constructor" side of the class).
|
|
return [
|
|
createSyntheticExpression(expr, getTypeOfSymbol(getSymbolOfNode(parent)))
|
|
];
|
|
case SyntaxKind.Parameter:
|
|
// A parameter declaration decorator will have three arguments (see
|
|
// `ParameterDecorator` in core.d.ts).
|
|
const func = <FunctionLikeDeclaration>parent.parent;
|
|
return [
|
|
createSyntheticExpression(expr, parent.parent.kind === SyntaxKind.Constructor ? getTypeOfSymbol(getSymbolOfNode(func)) : errorType),
|
|
createSyntheticExpression(expr, anyType),
|
|
createSyntheticExpression(expr, numberType)
|
|
];
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
// A method or accessor declaration decorator will have two or three arguments (see
|
|
// `PropertyDecorator` and `MethodDecorator` in core.d.ts). If we are emitting decorators
|
|
// for ES3, we will only pass two arguments.
|
|
const hasPropDesc = parent.kind !== SyntaxKind.PropertyDeclaration && languageVersion !== ScriptTarget.ES3;
|
|
return [
|
|
createSyntheticExpression(expr, getParentTypeOfClassElement(<ClassElement>parent)),
|
|
createSyntheticExpression(expr, getClassElementPropertyKeyType(<ClassElement>parent)),
|
|
createSyntheticExpression(expr, hasPropDesc ? createTypedPropertyDescriptorType(getTypeOfNode(parent)) : anyType)
|
|
];
|
|
}
|
|
return Debug.fail();
|
|
}
|
|
|
|
/**
|
|
* Returns the argument count for a decorator node that works like a function invocation.
|
|
*/
|
|
function getDecoratorArgumentCount(node: Decorator, signature: Signature) {
|
|
switch (node.parent.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
return 1;
|
|
case SyntaxKind.PropertyDeclaration:
|
|
return 2;
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
// For ES3 or decorators with only two parameters we supply only two arguments
|
|
return languageVersion === ScriptTarget.ES3 || signature.parameters.length <= 2 ? 2 : 3;
|
|
case SyntaxKind.Parameter:
|
|
return 3;
|
|
default:
|
|
return Debug.fail();
|
|
}
|
|
}
|
|
function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) {
|
|
let start: number;
|
|
let length: number;
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
|
|
if (isPropertyAccessExpression(node.expression)) {
|
|
const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name);
|
|
start = nameSpan.start;
|
|
length = doNotIncludeArguments ? nameSpan.length : node.end - start;
|
|
}
|
|
else {
|
|
const expressionSpan = getErrorSpanForNode(sourceFile, node.expression);
|
|
start = expressionSpan.start;
|
|
length = doNotIncludeArguments ? expressionSpan.length : node.end - start;
|
|
}
|
|
return { start, length, sourceFile };
|
|
}
|
|
function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation {
|
|
if (isCallExpression(node)) {
|
|
const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node);
|
|
return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3);
|
|
}
|
|
else {
|
|
return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3);
|
|
}
|
|
}
|
|
|
|
function getArgumentArityError(node: CallLikeExpression, signatures: readonly Signature[], args: readonly Expression[]) {
|
|
let min = Number.POSITIVE_INFINITY;
|
|
let max = Number.NEGATIVE_INFINITY;
|
|
let belowArgCount = Number.NEGATIVE_INFINITY;
|
|
let aboveArgCount = Number.POSITIVE_INFINITY;
|
|
|
|
let argCount = args.length;
|
|
let closestSignature: Signature | undefined;
|
|
for (const sig of signatures) {
|
|
const minCount = getMinArgumentCount(sig);
|
|
const maxCount = getParameterCount(sig);
|
|
if (minCount < argCount && minCount > belowArgCount) belowArgCount = minCount;
|
|
if (argCount < maxCount && maxCount < aboveArgCount) aboveArgCount = maxCount;
|
|
if (minCount < min) {
|
|
min = minCount;
|
|
closestSignature = sig;
|
|
}
|
|
max = Math.max(max, maxCount);
|
|
}
|
|
|
|
const hasRestParameter = some(signatures, hasEffectiveRestParameter);
|
|
const paramRange = hasRestParameter ? min :
|
|
min < max ? min + "-" + max :
|
|
min;
|
|
const hasSpreadArgument = getSpreadArgumentIndex(args) > -1;
|
|
if (argCount <= max && hasSpreadArgument) {
|
|
argCount--;
|
|
}
|
|
|
|
let spanArray: NodeArray<Node>;
|
|
let related: DiagnosticWithLocation | undefined;
|
|
|
|
const error = hasRestParameter || hasSpreadArgument ? hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more :
|
|
hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 :
|
|
Diagnostics.Expected_0_arguments_but_got_1_or_more : Diagnostics.Expected_0_arguments_but_got_1;
|
|
|
|
if (closestSignature && getMinArgumentCount(closestSignature) > argCount && closestSignature.declaration) {
|
|
const paramDecl = closestSignature.declaration.parameters[closestSignature.thisParameter ? argCount + 1 : argCount];
|
|
if (paramDecl) {
|
|
related = createDiagnosticForNode(
|
|
paramDecl,
|
|
isBindingPattern(paramDecl.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided : Diagnostics.An_argument_for_0_was_not_provided,
|
|
!paramDecl.name ? argCount : !isBindingPattern(paramDecl.name) ? idText(getFirstIdentifier(paramDecl.name)) : undefined
|
|
);
|
|
}
|
|
}
|
|
if (min < argCount && argCount < max) {
|
|
return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount);
|
|
}
|
|
|
|
if (!hasSpreadArgument && argCount < min) {
|
|
const diagnostic = getDiagnosticForCallNode(node, error, paramRange, argCount);
|
|
return related ? addRelatedInfo(diagnostic, related) : diagnostic;
|
|
}
|
|
|
|
if (hasRestParameter || hasSpreadArgument) {
|
|
spanArray = createNodeArray(args);
|
|
if (hasSpreadArgument && argCount) {
|
|
const nextArg = elementAt(args, getSpreadArgumentIndex(args) + 1) || undefined;
|
|
spanArray = createNodeArray(args.slice(max > argCount && nextArg ? args.indexOf(nextArg) : Math.min(max, args.length - 1)));
|
|
}
|
|
}
|
|
else {
|
|
spanArray = createNodeArray(args.slice(max));
|
|
}
|
|
|
|
spanArray.pos = first(spanArray).pos;
|
|
spanArray.end = last(spanArray).end;
|
|
if (spanArray.end === spanArray.pos) {
|
|
spanArray.end++;
|
|
}
|
|
const diagnostic = createDiagnosticForNodeArray(
|
|
getSourceFileOfNode(node), spanArray, error, paramRange, argCount);
|
|
return related ? addRelatedInfo(diagnostic, related) : diagnostic;
|
|
}
|
|
|
|
function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray<TypeNode>) {
|
|
const argCount = typeArguments.length;
|
|
// No overloads exist
|
|
if (signatures.length === 1) {
|
|
const sig = signatures[0];
|
|
const min = getMinTypeArgumentCount(sig.typeParameters);
|
|
const max = length(sig.typeParameters);
|
|
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min , argCount);
|
|
}
|
|
// Overloads exist
|
|
let belowArgCount = -Infinity;
|
|
let aboveArgCount = Infinity;
|
|
for (const sig of signatures) {
|
|
const min = getMinTypeArgumentCount(sig.typeParameters);
|
|
const max = length(sig.typeParameters);
|
|
if (min > argCount) {
|
|
aboveArgCount = Math.min(aboveArgCount, min);
|
|
}
|
|
else if (max < argCount) {
|
|
belowArgCount = Math.max(belowArgCount, max);
|
|
}
|
|
}
|
|
if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) {
|
|
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount);
|
|
}
|
|
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount);
|
|
}
|
|
|
|
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature {
|
|
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
|
|
const isDecorator = node.kind === SyntaxKind.Decorator;
|
|
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
|
|
const reportErrors = !candidatesOutArray;
|
|
|
|
let typeArguments: NodeArray<TypeNode> | undefined;
|
|
|
|
if (!isDecorator) {
|
|
typeArguments = (<CallExpression>node).typeArguments;
|
|
|
|
// We already perform checking on the type arguments on the class declaration itself.
|
|
if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword) {
|
|
forEach(typeArguments, checkSourceElement);
|
|
}
|
|
}
|
|
|
|
const candidates = candidatesOutArray || [];
|
|
// reorderCandidates fills up the candidates array directly
|
|
reorderCandidates(signatures, candidates, callChainFlags);
|
|
if (!candidates.length) {
|
|
if (reportErrors) {
|
|
diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures));
|
|
}
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
const args = getEffectiveCallArguments(node);
|
|
|
|
// The excludeArgument array contains true for each context sensitive argument (an argument
|
|
// is context sensitive it is susceptible to a one-time permanent contextual typing).
|
|
//
|
|
// The idea is that we will perform type argument inference & assignability checking once
|
|
// without using the susceptible parameters that are functions, and once more for those
|
|
// parameters, contextually typing each as we go along.
|
|
//
|
|
// For a tagged template, then the first argument be 'undefined' if necessary because it
|
|
// represents a TemplateStringsArray.
|
|
//
|
|
// For a decorator, no arguments are susceptible to contextual typing due to the fact
|
|
// decorators are applied to a declaration by the emitter, and not to an expression.
|
|
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
|
|
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal;
|
|
|
|
// The following variables are captured and modified by calls to chooseOverload.
|
|
// If overload resolution or type argument inference fails, we want to report the
|
|
// best error possible. The best error is one which says that an argument was not
|
|
// assignable to a parameter. This implies that everything else about the overload
|
|
// was fine. So if there is any overload that is only incorrect because of an
|
|
// argument, we will report an error on that one.
|
|
//
|
|
// function foo(s: string): void;
|
|
// function foo(n: number): void; // Report argument error on this overload
|
|
// function foo(): void;
|
|
// foo(true);
|
|
//
|
|
// If none of the overloads even made it that far, there are two possibilities.
|
|
// There was a problem with type arguments for some overload, in which case
|
|
// report an error on that. Or none of the overloads even had correct arity,
|
|
// in which case give an arity error.
|
|
//
|
|
// function foo<T extends string>(x: T): void; // Report type argument error
|
|
// function foo(): void;
|
|
// foo<number>(0);
|
|
//
|
|
let candidatesForArgumentError: Signature[] | undefined;
|
|
let candidateForArgumentArityError: Signature | undefined;
|
|
let candidateForTypeArgumentError: Signature | undefined;
|
|
let result: Signature | undefined;
|
|
|
|
// If we are in signature help, a trailing comma indicates that we intend to provide another argument,
|
|
// so we will only accept overloads with arity at least 1 higher than the current number of provided arguments.
|
|
const signatureHelpTrailingComma =
|
|
!!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;
|
|
|
|
// Section 4.12.1:
|
|
// if the candidate list contains one or more signatures for which the type of each argument
|
|
// expression is a subtype of each corresponding parameter type, the return type of the first
|
|
// of those signatures becomes the return type of the function call.
|
|
// Otherwise, the return type of the first signature in the candidate list becomes the return
|
|
// type of the function call.
|
|
//
|
|
// Whether the call is an error is determined by assignability of the arguments. The subtype pass
|
|
// is just important for choosing the best signature. So in the case where there is only one
|
|
// signature, the subtype pass is useless. So skipping it is an optimization.
|
|
if (candidates.length > 1) {
|
|
result = chooseOverload(candidates, subtypeRelation, signatureHelpTrailingComma);
|
|
}
|
|
if (!result) {
|
|
result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma);
|
|
}
|
|
if (result) {
|
|
return result;
|
|
}
|
|
|
|
// No signatures were applicable. Now report errors based on the last applicable signature with
|
|
// no arguments excluded from assignability checks.
|
|
// If candidate is undefined, it means that no candidates had a suitable arity. In that case,
|
|
// skip the checkApplicableSignature check.
|
|
if (reportErrors) {
|
|
if (candidatesForArgumentError) {
|
|
if (candidatesForArgumentError.length === 1 || candidatesForArgumentError.length > 3) {
|
|
const last = candidatesForArgumentError[candidatesForArgumentError.length - 1];
|
|
let chain: DiagnosticMessageChain | undefined;
|
|
if (candidatesForArgumentError.length > 3) {
|
|
chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error);
|
|
chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call);
|
|
}
|
|
const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain);
|
|
if (diags) {
|
|
for (const d of diags) {
|
|
if (last.declaration && candidatesForArgumentError.length > 3) {
|
|
addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here));
|
|
}
|
|
diagnostics.add(d);
|
|
}
|
|
}
|
|
else {
|
|
Debug.fail("No error for last overload signature");
|
|
}
|
|
}
|
|
else {
|
|
const allDiagnostics: (readonly DiagnosticRelatedInformation[])[] = [];
|
|
let max = 0;
|
|
let min = Number.MAX_VALUE;
|
|
let minIndex = 0;
|
|
let i = 0;
|
|
for (const c of candidatesForArgumentError) {
|
|
const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c));
|
|
const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain);
|
|
if (diags) {
|
|
if (diags.length <= min) {
|
|
min = diags.length;
|
|
minIndex = i;
|
|
}
|
|
max = Math.max(max, diags.length);
|
|
allDiagnostics.push(diags);
|
|
}
|
|
else {
|
|
Debug.fail("No error for 3 or fewer overload signatures");
|
|
}
|
|
i++;
|
|
}
|
|
|
|
const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics);
|
|
Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures");
|
|
const chain = chainDiagnosticMessages(
|
|
map(diags, d => typeof d.messageText === "string" ? (d as DiagnosticMessageChain) : d.messageText),
|
|
Diagnostics.No_overload_matches_this_call);
|
|
const related = flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[];
|
|
if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) {
|
|
const { file, start, length } = diags[0];
|
|
diagnostics.add({ file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related });
|
|
}
|
|
else {
|
|
diagnostics.add(createDiagnosticForNodeFromMessageChain(node, chain, related));
|
|
}
|
|
}
|
|
}
|
|
else if (candidateForArgumentArityError) {
|
|
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args));
|
|
}
|
|
else if (candidateForTypeArgumentError) {
|
|
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError);
|
|
}
|
|
else {
|
|
const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments));
|
|
if (signaturesWithCorrectTypeArgumentArity.length === 0) {
|
|
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!));
|
|
}
|
|
else if (!isDecorator) {
|
|
diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args));
|
|
}
|
|
else if (fallbackError) {
|
|
diagnostics.add(getDiagnosticForCallNode(node, fallbackError));
|
|
}
|
|
}
|
|
}
|
|
|
|
return getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
|
|
|
|
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
|
|
candidatesForArgumentError = undefined;
|
|
candidateForArgumentArityError = undefined;
|
|
candidateForTypeArgumentError = undefined;
|
|
|
|
if (isSingleNonGenericCandidate) {
|
|
const candidate = candidates[0];
|
|
if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) {
|
|
return undefined;
|
|
}
|
|
if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) {
|
|
candidatesForArgumentError = [candidate];
|
|
return undefined;
|
|
}
|
|
return candidate;
|
|
}
|
|
|
|
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
|
|
const candidate = candidates[candidateIndex];
|
|
if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) {
|
|
continue;
|
|
}
|
|
|
|
let checkCandidate: Signature;
|
|
let inferenceContext: InferenceContext | undefined;
|
|
|
|
if (candidate.typeParameters) {
|
|
let typeArgumentTypes: Type[] | undefined;
|
|
if (some(typeArguments)) {
|
|
typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false);
|
|
if (!typeArgumentTypes) {
|
|
candidateForTypeArgumentError = candidate;
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
|
|
typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext);
|
|
argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal;
|
|
}
|
|
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters);
|
|
// If the original signature has a generic rest type, instantiation may produce a
|
|
// signature with different arity and we need to perform another arity check.
|
|
if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) {
|
|
candidateForArgumentArityError = checkCandidate;
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
checkCandidate = candidate;
|
|
}
|
|
if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) {
|
|
// Give preference to error candidates that have no rest parameters (as they are more specific)
|
|
(candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate);
|
|
continue;
|
|
}
|
|
if (argCheckMode) {
|
|
// If one or more context sensitive arguments were excluded, we start including
|
|
// them now (and keeping do so for any subsequent candidates) and perform a second
|
|
// round of type inference and applicability checking for this particular candidate.
|
|
argCheckMode = CheckMode.Normal;
|
|
if (inferenceContext) {
|
|
const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext);
|
|
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters);
|
|
// If the original signature has a generic rest type, instantiation may produce a
|
|
// signature with different arity and we need to perform another arity check.
|
|
if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) {
|
|
candidateForArgumentArityError = checkCandidate;
|
|
continue;
|
|
}
|
|
}
|
|
if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) {
|
|
// Give preference to error candidates that have no rest parameters (as they are more specific)
|
|
(candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate);
|
|
continue;
|
|
}
|
|
}
|
|
candidates[candidateIndex] = checkCandidate;
|
|
return checkCandidate;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
// No signature was applicable. We have already reported the errors for the invalid signature.
|
|
function getCandidateForOverloadFailure(
|
|
node: CallLikeExpression,
|
|
candidates: Signature[],
|
|
args: readonly Expression[],
|
|
hasCandidatesOutArray: boolean,
|
|
): Signature {
|
|
Debug.assert(candidates.length > 0); // Else should not have called this.
|
|
checkNodeDeferred(node);
|
|
// Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine.
|
|
// Don't do this if there is a `candidatesOutArray`,
|
|
// because then we want the chosen best candidate to be one of the overloads, not a combination.
|
|
return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters)
|
|
? pickLongestCandidateSignature(node, candidates, args)
|
|
: createUnionOfSignaturesForOverloadFailure(candidates);
|
|
}
|
|
|
|
function createUnionOfSignaturesForOverloadFailure(candidates: readonly Signature[]): Signature {
|
|
const thisParameters = mapDefined(candidates, c => c.thisParameter);
|
|
let thisParameter: Symbol | undefined;
|
|
if (thisParameters.length) {
|
|
thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter));
|
|
}
|
|
const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
|
|
const parameters: Symbol[] = [];
|
|
for (let i = 0; i < maxNonRestParam; i++) {
|
|
const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ?
|
|
i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) :
|
|
i < s.parameters.length ? s.parameters[i] : undefined);
|
|
Debug.assert(symbols.length !== 0);
|
|
parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i))));
|
|
}
|
|
const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined);
|
|
let flags = SignatureFlags.None;
|
|
if (restParameterSymbols.length !== 0) {
|
|
const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype));
|
|
parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type));
|
|
flags |= SignatureFlags.HasRestParameter;
|
|
}
|
|
if (candidates.some(signatureHasLiteralTypes)) {
|
|
flags |= SignatureFlags.HasLiteralTypes;
|
|
}
|
|
return createSignature(
|
|
candidates[0].declaration,
|
|
/*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`.
|
|
thisParameter,
|
|
parameters,
|
|
/*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)),
|
|
/*typePredicate*/ undefined,
|
|
minArgumentCount,
|
|
flags);
|
|
}
|
|
|
|
function getNumNonRestParameters(signature: Signature): number {
|
|
const numParams = signature.parameters.length;
|
|
return signatureHasRestParameter(signature) ? numParams - 1 : numParams;
|
|
}
|
|
|
|
function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol {
|
|
return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype));
|
|
}
|
|
|
|
function createCombinedSymbolForOverloadFailure(sources: readonly Symbol[], type: Type): Symbol {
|
|
// This function is currently only used for erroneous overloads, so it's good enough to just use the first source.
|
|
return createSymbolWithType(first(sources), type);
|
|
}
|
|
|
|
function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[]): Signature {
|
|
// Pick the longest signature. This way we can get a contextual type for cases like:
|
|
// declare function f(a: { xa: number; xb: number; }, b: number);
|
|
// f({ |
|
|
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
|
|
// declare function f<T>(k: keyof T);
|
|
// f<Foo>("
|
|
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
|
|
const candidate = candidates[bestIndex];
|
|
const { typeParameters } = candidate;
|
|
if (!typeParameters) {
|
|
return candidate;
|
|
}
|
|
|
|
const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined;
|
|
const instantiated = typeArgumentNodes
|
|
? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node)))
|
|
: inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args);
|
|
candidates[bestIndex] = instantiated;
|
|
return instantiated;
|
|
}
|
|
|
|
function getTypeArgumentsFromNodes(typeArgumentNodes: readonly TypeNode[], typeParameters: readonly TypeParameter[], isJs: boolean): readonly Type[] {
|
|
const typeArguments = typeArgumentNodes.map(getTypeOfNode);
|
|
while (typeArguments.length > typeParameters.length) {
|
|
typeArguments.pop();
|
|
}
|
|
while (typeArguments.length < typeParameters.length) {
|
|
typeArguments.push(getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs));
|
|
}
|
|
return typeArguments;
|
|
}
|
|
|
|
function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[]): Signature {
|
|
const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
|
|
const typeArgumentTypes = inferTypeArguments(node, candidate, args, CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext);
|
|
return createSignatureInstantiation(candidate, typeArgumentTypes);
|
|
}
|
|
|
|
function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
|
|
let maxParamsIndex = -1;
|
|
let maxParams = -1;
|
|
|
|
for (let i = 0; i < candidates.length; i++) {
|
|
const candidate = candidates[i];
|
|
const paramCount = getParameterCount(candidate);
|
|
if (hasEffectiveRestParameter(candidate) || paramCount >= argsCount) {
|
|
return i;
|
|
}
|
|
if (paramCount > maxParams) {
|
|
maxParams = paramCount;
|
|
maxParamsIndex = i;
|
|
}
|
|
}
|
|
|
|
return maxParamsIndex;
|
|
}
|
|
|
|
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
|
if (node.expression.kind === SyntaxKind.SuperKeyword) {
|
|
const superType = checkSuperExpression(node.expression);
|
|
if (isTypeAny(superType)) {
|
|
for (const arg of node.arguments) {
|
|
checkExpression(arg); // Still visit arguments so they get marked for visibility, etc
|
|
}
|
|
return anySignature;
|
|
}
|
|
if (superType !== errorType) {
|
|
// In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated
|
|
// with the type arguments specified in the extends clause.
|
|
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
|
|
if (baseTypeNode) {
|
|
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
|
|
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None);
|
|
}
|
|
}
|
|
return resolveUntypedCall(node);
|
|
}
|
|
|
|
let callChainFlags: SignatureFlags;
|
|
let funcType = checkExpression(node.expression);
|
|
if (isCallChain(node)) {
|
|
const nonOptionalType = getOptionalExpressionType(funcType, node.expression);
|
|
callChainFlags = nonOptionalType === funcType ? SignatureFlags.None :
|
|
isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain :
|
|
SignatureFlags.IsInnerCallChain;
|
|
funcType = nonOptionalType;
|
|
}
|
|
else {
|
|
callChainFlags = SignatureFlags.None;
|
|
}
|
|
|
|
funcType = checkNonNullTypeWithReporter(
|
|
funcType,
|
|
node.expression,
|
|
reportCannotInvokePossiblyNullOrUndefinedError
|
|
);
|
|
|
|
if (funcType === silentNeverType) {
|
|
return silentNeverSignature;
|
|
}
|
|
|
|
const apparentType = getApparentType(funcType);
|
|
if (apparentType === errorType) {
|
|
// Another error has already been reported
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
// Technically, this signatures list may be incomplete. We are taking the apparent type,
|
|
// but we are not including call signatures that may have been added to the Object or
|
|
// Function interface, since they have none by default. This is a bit of a leap of faith
|
|
// that the user will not add any.
|
|
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
|
|
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
|
|
|
|
// TS 1.0 Spec: 4.12
|
|
// In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual
|
|
// types are provided for the argument expressions, and the result is always of type Any.
|
|
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
|
|
// The unknownType indicates that an error already occurred (and was reported). No
|
|
// need to report another error in this case.
|
|
if (funcType !== errorType && node.typeArguments) {
|
|
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
|
|
}
|
|
return resolveUntypedCall(node);
|
|
}
|
|
// If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call.
|
|
// TypeScript employs overload resolution in typed function calls in order to support functions
|
|
// with multiple call signatures.
|
|
if (!callSignatures.length) {
|
|
if (numConstructSignatures) {
|
|
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
|
|
}
|
|
else {
|
|
let relatedInformation: DiagnosticRelatedInformation | undefined;
|
|
if (node.arguments.length === 1) {
|
|
const text = getSourceFileOfNode(node).text;
|
|
if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) {
|
|
relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.Are_you_missing_a_semicolon);
|
|
}
|
|
}
|
|
invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation);
|
|
}
|
|
return resolveErrorCall(node);
|
|
}
|
|
// When a call to a generic function is an argument to an outer call to a generic function for which
|
|
// inference is in process, we have a choice to make. If the inner call relies on inferences made from
|
|
// its contextual type to its return type, deferring the inner call processing allows the best possible
|
|
// contextual type to accumulate. But if the outer call relies on inferences made from the return type of
|
|
// the inner call, the inner call should be processed early. There's no sure way to know which choice is
|
|
// right (only a full unification algorithm can determine that), so we resort to the following heuristic:
|
|
// If no type arguments are specified in the inner call and at least one call signature is generic and
|
|
// returns a function type, we choose to defer processing. This narrowly permits function composition
|
|
// operators to flow inferences through return types, but otherwise processes calls right away. We
|
|
// use the resolvingSignature singleton to indicate that we deferred processing. This result will be
|
|
// propagated out and eventually turned into nonInferrableType (a type that is assignable to anything and
|
|
// from which we never make inferences).
|
|
if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) {
|
|
skippedGenericFunction(node, checkMode);
|
|
return resolvingSignature;
|
|
}
|
|
// If the function is explicitly marked with `@class`, then it must be constructed.
|
|
if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) {
|
|
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags);
|
|
}
|
|
|
|
function isGenericFunctionReturningFunction(signature: Signature) {
|
|
return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature)));
|
|
}
|
|
|
|
/**
|
|
* TS 1.0 spec: 4.12
|
|
* If FuncExpr is of type Any, or of an object type that has no call or construct signatures
|
|
* but is a subtype of the Function interface, the call is an untyped function call.
|
|
*/
|
|
function isUntypedFunctionCall(funcType: Type, apparentFuncType: Type, numCallSignatures: number, numConstructSignatures: number): boolean {
|
|
// We exclude union types because we may have a union of function types that happen to have no common signatures.
|
|
return isTypeAny(funcType) || isTypeAny(apparentFuncType) && !!(funcType.flags & TypeFlags.TypeParameter) ||
|
|
!numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType);
|
|
}
|
|
|
|
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
|
if (node.arguments && languageVersion < ScriptTarget.ES5) {
|
|
const spreadIndex = getSpreadArgumentIndex(node.arguments);
|
|
if (spreadIndex >= 0) {
|
|
error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher);
|
|
}
|
|
}
|
|
|
|
let expressionType = checkNonNullExpression(node.expression);
|
|
if (expressionType === silentNeverType) {
|
|
return silentNeverSignature;
|
|
}
|
|
|
|
// If expressionType's apparent type(section 3.8.1) is an object type with one or
|
|
// more construct signatures, the expression is processed in the same manner as a
|
|
// function call, but using the construct signatures as the initial set of candidate
|
|
// signatures for overload resolution. The result type of the function call becomes
|
|
// the result type of the operation.
|
|
expressionType = getApparentType(expressionType);
|
|
if (expressionType === errorType) {
|
|
// Another error has already been reported
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
// TS 1.0 spec: 4.11
|
|
// If expressionType is of type Any, Args can be any argument
|
|
// list and the result of the operation is of type Any.
|
|
if (isTypeAny(expressionType)) {
|
|
if (node.typeArguments) {
|
|
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
|
|
}
|
|
return resolveUntypedCall(node);
|
|
}
|
|
|
|
// Technically, this signatures list may be incomplete. We are taking the apparent type,
|
|
// but we are not including construct signatures that may have been added to the Object or
|
|
// Function interface, since they have none by default. This is a bit of a leap of faith
|
|
// that the user will not add any.
|
|
const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct);
|
|
if (constructSignatures.length) {
|
|
if (!isConstructorAccessible(node, constructSignatures[0])) {
|
|
return resolveErrorCall(node);
|
|
}
|
|
// If the expression is a class of abstract type, then it cannot be instantiated.
|
|
// Note, only class declarations can be declared abstract.
|
|
// In the case of a merged class-module or class-interface declaration,
|
|
// only the class declaration node will have the Abstract flag set.
|
|
const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol);
|
|
if (valueDecl && hasModifier(valueDecl, ModifierFlags.Abstract)) {
|
|
error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class);
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
|
|
}
|
|
|
|
// If expressionType's apparent type is an object type with no construct signatures but
|
|
// one or more call signatures, the expression is processed as a function call. A compile-time
|
|
// error occurs if the result of the function call is not Void. The type of the result of the
|
|
// operation is Any. It is an error to have a Void this type.
|
|
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
|
|
if (callSignatures.length) {
|
|
const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
|
|
if (!noImplicitAny) {
|
|
if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
|
|
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
|
|
}
|
|
if (getThisTypeOfSignature(signature) === voidType) {
|
|
error(node, Diagnostics.A_function_that_is_called_with_the_new_keyword_cannot_have_a_this_type_that_is_void);
|
|
}
|
|
}
|
|
return signature;
|
|
}
|
|
|
|
invocationError(node.expression, expressionType, SignatureKind.Construct);
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
function typeHasProtectedAccessibleBase(target: Symbol, type: InterfaceType): boolean {
|
|
const baseTypes = getBaseTypes(type);
|
|
if (!length(baseTypes)) {
|
|
return false;
|
|
}
|
|
const firstBase = baseTypes[0];
|
|
if (firstBase.flags & TypeFlags.Intersection) {
|
|
const types = (firstBase as IntersectionType).types;
|
|
const mixinFlags = findMixins(types);
|
|
let i = 0;
|
|
for (const intersectionMember of (firstBase as IntersectionType).types) {
|
|
// We want to ignore mixin ctors
|
|
if (!mixinFlags[i]) {
|
|
if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) {
|
|
if (intersectionMember.symbol === target) {
|
|
return true;
|
|
}
|
|
if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return false;
|
|
}
|
|
if (firstBase.symbol === target) {
|
|
return true;
|
|
}
|
|
return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType);
|
|
}
|
|
|
|
function isConstructorAccessible(node: NewExpression, signature: Signature) {
|
|
if (!signature || !signature.declaration) {
|
|
return true;
|
|
}
|
|
|
|
const declaration = signature.declaration;
|
|
const modifiers = getSelectedModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier);
|
|
|
|
// (1) Public constructors and (2) constructor functions are always accessible.
|
|
if (!modifiers || declaration.kind !== SyntaxKind.Constructor) {
|
|
return true;
|
|
}
|
|
|
|
const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!;
|
|
const declaringClass = <InterfaceType>getDeclaredTypeOfSymbol(declaration.parent.symbol);
|
|
|
|
// A private or protected constructor can only be instantiated within its own class (or a subclass, for protected)
|
|
if (!isNodeWithinClass(node, declaringClassDeclaration)) {
|
|
const containingClass = getContainingClass(node);
|
|
if (containingClass && modifiers & ModifierFlags.Protected) {
|
|
const containingType = getTypeOfNode(containingClass);
|
|
if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (modifiers & ModifierFlags.Private) {
|
|
error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass));
|
|
}
|
|
if (modifiers & ModifierFlags.Protected) {
|
|
error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function invocationErrorDetails(apparentType: Type, kind: SignatureKind): { messageChain: DiagnosticMessageChain, relatedMessage: DiagnosticMessage | undefined } {
|
|
let errorInfo: DiagnosticMessageChain | undefined;
|
|
const isCall = kind === SignatureKind.Call;
|
|
const awaitedType = getAwaitedType(apparentType);
|
|
const maybeMissingAwait = awaitedType && getSignaturesOfType(awaitedType, kind).length > 0;
|
|
if (apparentType.flags & TypeFlags.Union) {
|
|
const types = (apparentType as UnionType).types;
|
|
let hasSignatures = false;
|
|
for (const constituent of types) {
|
|
const signatures = getSignaturesOfType(constituent, kind);
|
|
if (signatures.length !== 0) {
|
|
hasSignatures = true;
|
|
if (errorInfo) {
|
|
// Bail early if we already have an error, no chance of "No constituent of type is callable"
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
// Error on the first non callable constituent only
|
|
if (!errorInfo) {
|
|
errorInfo = chainDiagnosticMessages(
|
|
errorInfo,
|
|
isCall ?
|
|
Diagnostics.Type_0_has_no_call_signatures :
|
|
Diagnostics.Type_0_has_no_construct_signatures,
|
|
typeToString(constituent)
|
|
);
|
|
errorInfo = chainDiagnosticMessages(
|
|
errorInfo,
|
|
isCall ?
|
|
Diagnostics.Not_all_constituents_of_type_0_are_callable :
|
|
Diagnostics.Not_all_constituents_of_type_0_are_constructable,
|
|
typeToString(apparentType)
|
|
);
|
|
}
|
|
if (hasSignatures) {
|
|
// Bail early if we already found a siganture, no chance of "No constituent of type is callable"
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!hasSignatures) {
|
|
errorInfo = chainDiagnosticMessages(
|
|
/* detials */ undefined,
|
|
isCall ?
|
|
Diagnostics.No_constituent_of_type_0_is_callable :
|
|
Diagnostics.No_constituent_of_type_0_is_constructable,
|
|
typeToString(apparentType)
|
|
);
|
|
}
|
|
if (!errorInfo) {
|
|
errorInfo = chainDiagnosticMessages(
|
|
errorInfo,
|
|
isCall ?
|
|
Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other :
|
|
Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other,
|
|
typeToString(apparentType)
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
errorInfo = chainDiagnosticMessages(
|
|
errorInfo,
|
|
isCall ?
|
|
Diagnostics.Type_0_has_no_call_signatures :
|
|
Diagnostics.Type_0_has_no_construct_signatures,
|
|
typeToString(apparentType)
|
|
);
|
|
}
|
|
return {
|
|
messageChain: chainDiagnosticMessages(
|
|
errorInfo,
|
|
isCall ? Diagnostics.This_expression_is_not_callable : Diagnostics.This_expression_is_not_constructable
|
|
),
|
|
relatedMessage: maybeMissingAwait ? Diagnostics.Did_you_forget_to_use_await : undefined,
|
|
};
|
|
}
|
|
function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) {
|
|
const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(apparentType, kind);
|
|
const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain);
|
|
if (relatedInfo) {
|
|
addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo));
|
|
}
|
|
if (isCallExpression(errorTarget.parent)) {
|
|
const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true);
|
|
diagnostic.start = start;
|
|
diagnostic.length = length;
|
|
}
|
|
diagnostics.add(diagnostic);
|
|
invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic);
|
|
}
|
|
|
|
function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) {
|
|
if (!apparentType.symbol) {
|
|
return;
|
|
}
|
|
const importNode = getSymbolLinks(apparentType.symbol).originatingImport;
|
|
// Create a diagnostic on the originating import if possible onto which we can attach a quickfix
|
|
// An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site
|
|
if (importNode && !isImportCall(importNode)) {
|
|
const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind);
|
|
if (!sigs || !sigs.length) return;
|
|
|
|
addRelatedInfo(diagnostic,
|
|
createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead)
|
|
);
|
|
}
|
|
}
|
|
|
|
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
|
const tagType = checkExpression(node.tag);
|
|
const apparentType = getApparentType(tagType);
|
|
|
|
if (apparentType === errorType) {
|
|
// Another error has already been reported
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
|
|
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
|
|
|
|
if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) {
|
|
return resolveUntypedCall(node);
|
|
}
|
|
|
|
if (!callSignatures.length) {
|
|
invocationError(node.tag, apparentType, SignatureKind.Call);
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
|
|
}
|
|
|
|
/**
|
|
* Gets the localized diagnostic head message to use for errors when resolving a decorator as a call expression.
|
|
*/
|
|
function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) {
|
|
switch (node.parent.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression;
|
|
|
|
case SyntaxKind.Parameter:
|
|
return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression;
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
return Diagnostics.Unable_to_resolve_signature_of_property_decorator_when_called_as_an_expression;
|
|
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
return Diagnostics.Unable_to_resolve_signature_of_method_decorator_when_called_as_an_expression;
|
|
|
|
default:
|
|
return Debug.fail();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolves a decorator as if it were a call expression.
|
|
*/
|
|
function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
|
const funcType = checkExpression(node.expression);
|
|
const apparentType = getApparentType(funcType);
|
|
if (apparentType === errorType) {
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
|
|
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
|
|
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
|
|
return resolveUntypedCall(node);
|
|
}
|
|
|
|
if (isPotentiallyUncalledDecorator(node, callSignatures)) {
|
|
const nodeStr = getTextOfNode(node.expression, /*includeTrivia*/ false);
|
|
error(node, Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0, nodeStr);
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node);
|
|
if (!callSignatures.length) {
|
|
const errorDetails = invocationErrorDetails(apparentType, SignatureKind.Call);
|
|
const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage);
|
|
const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain);
|
|
if (errorDetails.relatedMessage) {
|
|
addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage));
|
|
}
|
|
diagnostics.add(diag);
|
|
invocationErrorRecovery(apparentType, SignatureKind.Call, diag);
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage);
|
|
}
|
|
|
|
function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature {
|
|
const namespace = getJsxNamespaceAt(node);
|
|
const exports = namespace && getExportsOfSymbol(namespace);
|
|
// We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration
|
|
// file would probably be preferable.
|
|
const typeSymbol = exports && getSymbol(exports, JsxNames.Element, SymbolFlags.Type);
|
|
const returnNode = typeSymbol && nodeBuilder.symbolToEntityName(typeSymbol, SymbolFlags.Type, node);
|
|
const declaration = createFunctionTypeNode(/*typeParameters*/ undefined,
|
|
[createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotdotdot*/ undefined, "props", /*questionMark*/ undefined, nodeBuilder.typeToTypeNode(result, node))],
|
|
returnNode ? createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : createKeywordTypeNode(SyntaxKind.AnyKeyword)
|
|
);
|
|
const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String);
|
|
parameterSymbol.type = result;
|
|
return createSignature(
|
|
declaration,
|
|
/*typeParameters*/ undefined,
|
|
/*thisParameter*/ undefined,
|
|
[parameterSymbol],
|
|
typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType,
|
|
/*returnTypePredicate*/ undefined,
|
|
1,
|
|
SignatureFlags.None
|
|
);
|
|
}
|
|
|
|
function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
|
if (isJsxIntrinsicIdentifier(node.tagName)) {
|
|
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
|
|
const fakeSignature = createSignatureForJSXIntrinsic(node, result);
|
|
checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes);
|
|
return fakeSignature;
|
|
}
|
|
const exprTypes = checkExpression(node.tagName);
|
|
const apparentType = getApparentType(exprTypes);
|
|
if (apparentType === errorType) {
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node);
|
|
if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) {
|
|
return resolveUntypedCall(node);
|
|
}
|
|
|
|
if (signatures.length === 0) {
|
|
// We found no signatures at all, which is an error
|
|
error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName));
|
|
return resolveErrorCall(node);
|
|
}
|
|
|
|
return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None);
|
|
}
|
|
|
|
/**
|
|
* Sometimes, we have a decorator that could accept zero arguments,
|
|
* but is receiving too many arguments as part of the decorator invocation.
|
|
* In those cases, a user may have meant to *call* the expression before using it as a decorator.
|
|
*/
|
|
function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) {
|
|
return signatures.length && every(signatures, signature =>
|
|
signature.minArgumentCount === 0 &&
|
|
!signatureHasRestParameter(signature) &&
|
|
signature.parameters.length < getDecoratorArgumentCount(decorator, signature));
|
|
}
|
|
|
|
function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
|
switch (node.kind) {
|
|
case SyntaxKind.CallExpression:
|
|
return resolveCallExpression(node, candidatesOutArray, checkMode);
|
|
case SyntaxKind.NewExpression:
|
|
return resolveNewExpression(node, candidatesOutArray, checkMode);
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode);
|
|
case SyntaxKind.Decorator:
|
|
return resolveDecorator(node, candidatesOutArray, checkMode);
|
|
case SyntaxKind.JsxOpeningElement:
|
|
case SyntaxKind.JsxSelfClosingElement:
|
|
return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode);
|
|
}
|
|
throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable.");
|
|
}
|
|
|
|
/**
|
|
* Resolve a signature of a given call-like expression.
|
|
* @param node a call-like expression to try resolve a signature for
|
|
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
|
|
* the function will fill it up with appropriate candidate signatures
|
|
* @return a signature of the call-like expression or undefined if one can't be found
|
|
*/
|
|
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature {
|
|
const links = getNodeLinks(node);
|
|
// If getResolvedSignature has already been called, we will have cached the resolvedSignature.
|
|
// However, it is possible that either candidatesOutArray was not passed in the first time,
|
|
// or that a different candidatesOutArray was passed in. Therefore, we need to redo the work
|
|
// to correctly fill the candidatesOutArray.
|
|
const cached = links.resolvedSignature;
|
|
if (cached && cached !== resolvingSignature && !candidatesOutArray) {
|
|
return cached;
|
|
}
|
|
links.resolvedSignature = resolvingSignature;
|
|
const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal);
|
|
// When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call
|
|
// resolution should be deferred.
|
|
if (result !== resolvingSignature) {
|
|
// If signature resolution originated in control flow type analysis (for example to compute the
|
|
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary
|
|
// types from the control flow analysis.
|
|
links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether a declaration can be treated as a constructor in a JavaScript
|
|
* file.
|
|
*/
|
|
function isJSConstructor(node: Node | undefined): node is FunctionDeclaration | FunctionExpression {
|
|
if (!node || !isInJSFile(node)) {
|
|
return false;
|
|
}
|
|
const func = isFunctionDeclaration(node) || isFunctionExpression(node) ? node :
|
|
isVariableDeclaration(node) && node.initializer && isFunctionExpression(node.initializer) ? node.initializer :
|
|
undefined;
|
|
if (func) {
|
|
// If the node has a @class tag, treat it like a constructor.
|
|
if (getJSDocClassTag(node)) return true;
|
|
|
|
// If the symbol of the node has members, treat it like a constructor.
|
|
const symbol = getSymbolOfNode(func);
|
|
return !!symbol && hasEntries(symbol.members);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function mergeJSSymbols(target: Symbol, source: Symbol | undefined) {
|
|
if (source) {
|
|
const links = getSymbolLinks(source);
|
|
if (!links.inferredClassSymbol || !links.inferredClassSymbol.has("" + getSymbolId(target))) {
|
|
const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol;
|
|
inferred.exports = inferred.exports || createSymbolTable();
|
|
inferred.members = inferred.members || createSymbolTable();
|
|
inferred.flags |= source.flags & SymbolFlags.Class;
|
|
if (hasEntries(source.exports)) {
|
|
mergeSymbolTable(inferred.exports, source.exports);
|
|
}
|
|
if (hasEntries(source.members)) {
|
|
mergeSymbolTable(inferred.members, source.members);
|
|
}
|
|
(links.inferredClassSymbol || (links.inferredClassSymbol = createMap<TransientSymbol>())).set("" + getSymbolId(inferred), inferred);
|
|
return inferred;
|
|
}
|
|
return links.inferredClassSymbol.get("" + getSymbolId(target));
|
|
}
|
|
}
|
|
|
|
function getAssignedClassSymbol(decl: Declaration): Symbol | undefined {
|
|
const assignmentSymbol = decl && decl.parent &&
|
|
(isFunctionDeclaration(decl) && getSymbolOfNode(decl) ||
|
|
isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) ||
|
|
isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent));
|
|
const prototype = assignmentSymbol && assignmentSymbol.exports && assignmentSymbol.exports.get("prototype" as __String);
|
|
const init = prototype && prototype.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration);
|
|
return init ? getSymbolOfNode(init) : undefined;
|
|
}
|
|
|
|
function getAssignedJSPrototype(node: Node) {
|
|
if (!node.parent) {
|
|
return false;
|
|
}
|
|
let parent: Node = node.parent;
|
|
while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) {
|
|
parent = parent.parent;
|
|
}
|
|
if (parent && isBinaryExpression(parent) && isPrototypeAccess(parent.left) && parent.operatorToken.kind === SyntaxKind.EqualsToken) {
|
|
const right = getInitializerOfBinaryExpression(parent);
|
|
return isObjectLiteralExpression(right) && right;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Syntactically and semantically checks a call or new expression.
|
|
* @param node The call/new expression to be checked.
|
|
* @returns On success, the expression's signature's return type. On failure, anyType.
|
|
*/
|
|
function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type {
|
|
if (!checkGrammarTypeArguments(node, node.typeArguments)) checkGrammarArguments(node.arguments);
|
|
|
|
const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode);
|
|
if (signature === resolvingSignature) {
|
|
// CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that
|
|
// returns a function type. We defer checking and return nonInferrableType.
|
|
return nonInferrableType;
|
|
}
|
|
|
|
if (node.expression.kind === SyntaxKind.SuperKeyword) {
|
|
return voidType;
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.NewExpression) {
|
|
const declaration = signature.declaration;
|
|
|
|
if (declaration &&
|
|
declaration.kind !== SyntaxKind.Constructor &&
|
|
declaration.kind !== SyntaxKind.ConstructSignature &&
|
|
declaration.kind !== SyntaxKind.ConstructorType &&
|
|
!isJSDocConstructSignature(declaration) &&
|
|
!isJSConstructor(declaration)) {
|
|
|
|
// When resolved signature is a call signature (and not a construct signature) the result type is any
|
|
if (noImplicitAny) {
|
|
error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type);
|
|
}
|
|
return anyType;
|
|
}
|
|
}
|
|
|
|
// In JavaScript files, calls to any identifier 'require' are treated as external module imports
|
|
if (isInJSFile(node) && isCommonJsRequire(node)) {
|
|
return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral);
|
|
}
|
|
|
|
const returnType = getReturnTypeOfSignature(signature);
|
|
// Treat any call to the global 'Symbol' function that is part of a const variable or readonly property
|
|
// as a fresh unique symbol literal type.
|
|
if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) {
|
|
return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent));
|
|
}
|
|
if (node.kind === SyntaxKind.CallExpression && node.parent.kind === SyntaxKind.ExpressionStatement &&
|
|
returnType.flags & TypeFlags.Void && getTypePredicateOfSignature(signature)) {
|
|
if (!isDottedName(node.expression)) {
|
|
error(node.expression, Diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name);
|
|
}
|
|
else if (!getEffectsSignature(node)) {
|
|
const diagnostic = error(node.expression, Diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation);
|
|
getTypeOfDottedName(node.expression, diagnostic);
|
|
}
|
|
}
|
|
|
|
if (isInJSFile(node)) {
|
|
const decl = getDeclarationOfExpando(node);
|
|
if (decl) {
|
|
const jsSymbol = getSymbolOfNode(decl);
|
|
if (jsSymbol && hasEntries(jsSymbol.exports)) {
|
|
const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined);
|
|
jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral;
|
|
return getIntersectionType([returnType, jsAssignmentType]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return returnType;
|
|
}
|
|
|
|
function isSymbolOrSymbolForCall(node: Node) {
|
|
if (!isCallExpression(node)) return false;
|
|
let left = node.expression;
|
|
if (isPropertyAccessExpression(left) && left.name.escapedText === "for") {
|
|
left = left.expression;
|
|
}
|
|
if (!isIdentifier(left) || left.escapedText !== "Symbol") {
|
|
return false;
|
|
}
|
|
|
|
// make sure `Symbol` is the global symbol
|
|
const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false);
|
|
if (!globalESSymbol) {
|
|
return false;
|
|
}
|
|
|
|
return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false);
|
|
}
|
|
|
|
function checkImportCallExpression(node: ImportCall): Type {
|
|
// Check grammar of dynamic import
|
|
if (!checkGrammarArguments(node.arguments)) checkGrammarImportCallExpression(node);
|
|
|
|
if (node.arguments.length === 0) {
|
|
return createPromiseReturnType(node, anyType);
|
|
}
|
|
const specifier = node.arguments[0];
|
|
const specifierType = checkExpressionCached(specifier);
|
|
// Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion
|
|
for (let i = 1; i < node.arguments.length; ++i) {
|
|
checkExpressionCached(node.arguments[i]);
|
|
}
|
|
|
|
if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) {
|
|
error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType));
|
|
}
|
|
|
|
// resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal
|
|
const moduleSymbol = resolveExternalModuleName(node, specifier);
|
|
if (moduleSymbol) {
|
|
const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false);
|
|
if (esModuleSymbol) {
|
|
return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol));
|
|
}
|
|
}
|
|
return createPromiseReturnType(node, anyType);
|
|
}
|
|
|
|
function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol): Type {
|
|
if (allowSyntheticDefaultImports && type && type !== errorType) {
|
|
const synthType = type as SyntheticDefaultModuleType;
|
|
if (!synthType.syntheticType) {
|
|
const file = find(originalSymbol.declarations, isSourceFile);
|
|
const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false);
|
|
if (hasSyntheticDefault) {
|
|
const memberTable = createSymbolTable();
|
|
const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default);
|
|
newSymbol.nameType = getLiteralType("default");
|
|
newSymbol.target = resolveSymbol(symbol);
|
|
memberTable.set(InternalSymbolName.Default, newSymbol);
|
|
const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type);
|
|
const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
|
|
anonymousSymbol.type = defaultContainingObject;
|
|
synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject;
|
|
}
|
|
else {
|
|
synthType.syntheticType = type;
|
|
}
|
|
}
|
|
return synthType.syntheticType;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function isCommonJsRequire(node: Node): boolean {
|
|
if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) {
|
|
return false;
|
|
}
|
|
|
|
// Make sure require is not a local function
|
|
if (!isIdentifier(node.expression)) return Debug.fail();
|
|
const resolvedRequire = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true)!; // TODO: GH#18217
|
|
if (resolvedRequire === requireSymbol) {
|
|
return true;
|
|
}
|
|
// project includes symbol named 'require' - make sure that it is ambient and local non-alias
|
|
if (resolvedRequire.flags & SymbolFlags.Alias) {
|
|
return false;
|
|
}
|
|
|
|
const targetDeclarationKind = resolvedRequire.flags & SymbolFlags.Function
|
|
? SyntaxKind.FunctionDeclaration
|
|
: resolvedRequire.flags & SymbolFlags.Variable
|
|
? SyntaxKind.VariableDeclaration
|
|
: SyntaxKind.Unknown;
|
|
if (targetDeclarationKind !== SyntaxKind.Unknown) {
|
|
const decl = getDeclarationOfKind(resolvedRequire, targetDeclarationKind)!;
|
|
// function/variable declaration should be ambient
|
|
return !!decl && !!(decl.flags & NodeFlags.Ambient);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
|
|
if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments);
|
|
if (languageVersion < ScriptTarget.ES2015) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
|
|
}
|
|
return getReturnTypeOfSignature(getResolvedSignature(node));
|
|
}
|
|
|
|
function checkAssertion(node: AssertionExpression) {
|
|
return checkAssertionWorker(node, node.type, node.expression);
|
|
}
|
|
|
|
function isValidConstAssertionArgument(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.BigIntLiteral:
|
|
case SyntaxKind.TrueKeyword:
|
|
case SyntaxKind.FalseKeyword:
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
return true;
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return isValidConstAssertionArgument((<ParenthesizedExpression>node).expression);
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
const op = (<PrefixUnaryExpression>node).operator;
|
|
const arg = (<PrefixUnaryExpression>node).operand;
|
|
return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) ||
|
|
op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral;
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
const expr = (<PropertyAccessExpression | ElementAccessExpression>node).expression;
|
|
if (isIdentifier(expr)) {
|
|
let symbol = getSymbolAtLocation(expr);
|
|
if (symbol && symbol.flags & SymbolFlags.Alias) {
|
|
symbol = resolveAlias(symbol);
|
|
}
|
|
return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) {
|
|
let exprType = checkExpression(expression, checkMode);
|
|
if (isConstTypeReference(type)) {
|
|
if (!isValidConstAssertionArgument(expression)) {
|
|
error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals);
|
|
}
|
|
return getRegularTypeOfLiteralType(exprType);
|
|
}
|
|
checkSourceElement(type);
|
|
exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType));
|
|
const targetType = getTypeFromTypeNode(type);
|
|
if (produceDiagnostics && targetType !== errorType) {
|
|
const widenedType = getWidenedType(exprType);
|
|
if (!isTypeComparableTo(targetType, widenedType)) {
|
|
checkTypeComparableTo(exprType, targetType, errNode,
|
|
Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first);
|
|
}
|
|
}
|
|
return targetType;
|
|
}
|
|
|
|
function checkNonNullAssertion(node: NonNullExpression) {
|
|
return getNonNullableType(checkExpression(node.expression));
|
|
}
|
|
|
|
function checkMetaProperty(node: MetaProperty): Type {
|
|
checkGrammarMetaProperty(node);
|
|
|
|
if (node.keywordToken === SyntaxKind.NewKeyword) {
|
|
return checkNewTargetMetaProperty(node);
|
|
}
|
|
|
|
if (node.keywordToken === SyntaxKind.ImportKeyword) {
|
|
return checkImportMetaProperty(node);
|
|
}
|
|
|
|
return Debug.assertNever(node.keywordToken);
|
|
}
|
|
|
|
function checkNewTargetMetaProperty(node: MetaProperty) {
|
|
const container = getNewTargetContainer(node);
|
|
if (!container) {
|
|
error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target");
|
|
return errorType;
|
|
}
|
|
else if (container.kind === SyntaxKind.Constructor) {
|
|
const symbol = getSymbolOfNode(container.parent as ClassLikeDeclaration);
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
else {
|
|
const symbol = getSymbolOfNode(container)!;
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
}
|
|
|
|
function checkImportMetaProperty(node: MetaProperty) {
|
|
if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) {
|
|
error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_esnext_or_system);
|
|
}
|
|
const file = getSourceFileOfNode(node);
|
|
Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag.");
|
|
Debug.assert(!!file.externalModuleIndicator, "Containing file should be a module.");
|
|
return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType;
|
|
}
|
|
|
|
function getTypeOfParameter(symbol: Symbol) {
|
|
const type = getTypeOfSymbol(symbol);
|
|
if (strictNullChecks) {
|
|
const declaration = symbol.valueDeclaration;
|
|
if (declaration && hasInitializer(declaration)) {
|
|
return getOptionalType(type);
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getParameterNameAtPosition(signature: Signature, pos: number) {
|
|
const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
|
|
if (pos < paramCount) {
|
|
return signature.parameters[pos].escapedName;
|
|
}
|
|
const restParameter = signature.parameters[paramCount] || unknownSymbol;
|
|
const restType = getTypeOfSymbol(restParameter);
|
|
if (isTupleType(restType)) {
|
|
const associatedNames = (<TupleType>(<TypeReference>restType).target).associatedNames;
|
|
const index = pos - paramCount;
|
|
return associatedNames && associatedNames[index] || restParameter.escapedName + "_" + index as __String;
|
|
}
|
|
return restParameter.escapedName;
|
|
}
|
|
|
|
function getTypeAtPosition(signature: Signature, pos: number): Type {
|
|
return tryGetTypeAtPosition(signature, pos) || anyType;
|
|
}
|
|
|
|
function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined {
|
|
const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
|
|
if (pos < paramCount) {
|
|
return getTypeOfParameter(signature.parameters[pos]);
|
|
}
|
|
if (signatureHasRestParameter(signature)) {
|
|
// We want to return the value undefined for an out of bounds parameter position,
|
|
// so we need to check bounds here before calling getIndexedAccessType (which
|
|
// otherwise would return the type 'undefined').
|
|
const restType = getTypeOfSymbol(signature.parameters[paramCount]);
|
|
const index = pos - paramCount;
|
|
if (!isTupleType(restType) || restType.target.hasRestElement || index < getTypeArguments(restType).length) {
|
|
return getIndexedAccessType(restType, getLiteralType(index));
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getRestTypeAtPosition(source: Signature, pos: number): Type {
|
|
const paramCount = getParameterCount(source);
|
|
const restType = getEffectiveRestType(source);
|
|
const nonRestCount = paramCount - (restType ? 1 : 0);
|
|
if (restType && pos === nonRestCount) {
|
|
return restType;
|
|
}
|
|
const types = [];
|
|
const names = [];
|
|
for (let i = pos; i < nonRestCount; i++) {
|
|
types.push(getTypeAtPosition(source, i));
|
|
names.push(getParameterNameAtPosition(source, i));
|
|
}
|
|
if (restType) {
|
|
types.push(getIndexedAccessType(restType, numberType));
|
|
names.push(getParameterNameAtPosition(source, nonRestCount));
|
|
}
|
|
const minArgumentCount = getMinArgumentCount(source);
|
|
const minLength = minArgumentCount < pos ? 0 : minArgumentCount - pos;
|
|
return createTupleType(types, minLength, !!restType, /*readonly*/ false, names);
|
|
}
|
|
|
|
function getParameterCount(signature: Signature) {
|
|
const length = signature.parameters.length;
|
|
if (signatureHasRestParameter(signature)) {
|
|
const restType = getTypeOfSymbol(signature.parameters[length - 1]);
|
|
if (isTupleType(restType)) {
|
|
return length + getTypeArguments(restType).length - 1;
|
|
}
|
|
}
|
|
return length;
|
|
}
|
|
|
|
function getMinArgumentCount(signature: Signature, strongArityForUntypedJS?: boolean) {
|
|
if (signatureHasRestParameter(signature)) {
|
|
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
|
|
if (isTupleType(restType)) {
|
|
const minLength = restType.target.minLength;
|
|
if (minLength > 0) {
|
|
return signature.parameters.length - 1 + minLength;
|
|
}
|
|
}
|
|
}
|
|
if (!strongArityForUntypedJS && signature.flags & SignatureFlags.IsUntypedSignatureInJSFile) {
|
|
return 0;
|
|
}
|
|
return signature.minArgumentCount;
|
|
}
|
|
|
|
function hasEffectiveRestParameter(signature: Signature) {
|
|
if (signatureHasRestParameter(signature)) {
|
|
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
|
|
return !isTupleType(restType) || restType.target.hasRestElement;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getEffectiveRestType(signature: Signature) {
|
|
if (signatureHasRestParameter(signature)) {
|
|
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
|
|
return isTupleType(restType) ? getRestArrayTypeOfTupleType(restType) : restType;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getNonArrayRestType(signature: Signature) {
|
|
const restType = getEffectiveRestType(signature);
|
|
return restType && !isArrayType(restType) && !isTypeAny(restType) ? restType : undefined;
|
|
}
|
|
|
|
function getTypeOfFirstParameterOfSignature(signature: Signature) {
|
|
return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType);
|
|
}
|
|
|
|
function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) {
|
|
return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType;
|
|
}
|
|
|
|
function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) {
|
|
const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
|
|
for (let i = 0; i < len; i++) {
|
|
const declaration = <ParameterDeclaration>signature.parameters[i].valueDeclaration;
|
|
if (declaration.type) {
|
|
const typeNode = getEffectiveTypeAnnotationNode(declaration);
|
|
if (typeNode) {
|
|
inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i));
|
|
}
|
|
}
|
|
}
|
|
const restType = getEffectiveRestType(context);
|
|
if (restType && restType.flags & TypeFlags.TypeParameter) {
|
|
// The contextual signature has a generic rest parameter. We first instantiate the contextual
|
|
// signature (without fixing type parameters) and assign types to contextually typed parameters.
|
|
const instantiatedContext = instantiateSignature(context, inferenceContext.nonFixingMapper);
|
|
assignContextualParameterTypes(signature, instantiatedContext);
|
|
// We then infer from a tuple type representing the parameters that correspond to the contextual
|
|
// rest parameter.
|
|
const restPos = getParameterCount(context) - 1;
|
|
inferTypes(inferenceContext.inferences, getRestTypeAtPosition(signature, restPos), restType);
|
|
}
|
|
}
|
|
|
|
function assignContextualParameterTypes(signature: Signature, context: Signature) {
|
|
signature.typeParameters = context.typeParameters;
|
|
if (context.thisParameter) {
|
|
const parameter = signature.thisParameter;
|
|
if (!parameter || parameter.valueDeclaration && !(<ParameterDeclaration>parameter.valueDeclaration).type) {
|
|
if (!parameter) {
|
|
signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined);
|
|
}
|
|
assignParameterType(signature.thisParameter!, getTypeOfSymbol(context.thisParameter));
|
|
}
|
|
}
|
|
const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
|
|
for (let i = 0; i < len; i++) {
|
|
const parameter = signature.parameters[i];
|
|
if (!getEffectiveTypeAnnotationNode(<ParameterDeclaration>parameter.valueDeclaration)) {
|
|
const contextualParameterType = tryGetTypeAtPosition(context, i);
|
|
assignParameterType(parameter, contextualParameterType);
|
|
}
|
|
}
|
|
if (signatureHasRestParameter(signature)) {
|
|
// parameter might be a transient symbol generated by use of `arguments` in the function body.
|
|
const parameter = last(signature.parameters);
|
|
if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode(<ParameterDeclaration>parameter.valueDeclaration)) {
|
|
const contextualParameterType = getRestTypeAtPosition(context, len);
|
|
assignParameterType(parameter, contextualParameterType);
|
|
}
|
|
}
|
|
}
|
|
|
|
function assignNonContextualParameterTypes(signature: Signature) {
|
|
if (signature.thisParameter) {
|
|
assignParameterType(signature.thisParameter);
|
|
}
|
|
for (const parameter of signature.parameters) {
|
|
assignParameterType(parameter);
|
|
}
|
|
}
|
|
|
|
function assignParameterType(parameter: Symbol, type?: Type) {
|
|
const links = getSymbolLinks(parameter);
|
|
if (!links.type) {
|
|
const declaration = parameter.valueDeclaration as ParameterDeclaration;
|
|
links.type = type || getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
|
|
if (declaration.name.kind !== SyntaxKind.Identifier) {
|
|
// if inference didn't come up with anything but unknown, fall back to the binding pattern if present.
|
|
if (links.type === unknownType) {
|
|
links.type = getTypeFromBindingPattern(declaration.name);
|
|
}
|
|
assignBindingElementTypes(declaration.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push
|
|
// the destructured type into the contained binding elements.
|
|
function assignBindingElementTypes(pattern: BindingPattern) {
|
|
for (const element of pattern.elements) {
|
|
if (!isOmittedExpression(element)) {
|
|
if (element.name.kind === SyntaxKind.Identifier) {
|
|
getSymbolLinks(getSymbolOfNode(element)).type = getTypeForBindingElement(element);
|
|
}
|
|
else {
|
|
assignBindingElementTypes(element.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function createPromiseType(promisedType: Type): Type {
|
|
// creates a `Promise<T>` type where `T` is the promisedType argument
|
|
const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true);
|
|
if (globalPromiseType !== emptyGenericType) {
|
|
// if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type
|
|
promisedType = getAwaitedType(promisedType) || unknownType;
|
|
return createTypeReference(globalPromiseType, [promisedType]);
|
|
}
|
|
|
|
return unknownType;
|
|
}
|
|
|
|
function createPromiseLikeType(promisedType: Type): Type {
|
|
// creates a `PromiseLike<T>` type where `T` is the promisedType argument
|
|
const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true);
|
|
if (globalPromiseLikeType !== emptyGenericType) {
|
|
// if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type
|
|
promisedType = getAwaitedType(promisedType) || unknownType;
|
|
return createTypeReference(globalPromiseLikeType, [promisedType]);
|
|
}
|
|
|
|
return unknownType;
|
|
}
|
|
|
|
function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) {
|
|
const promiseType = createPromiseType(promisedType);
|
|
if (promiseType === unknownType) {
|
|
error(func, isImportCall(func) ?
|
|
Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option :
|
|
Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option);
|
|
return errorType;
|
|
}
|
|
else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) {
|
|
error(func, isImportCall(func) ?
|
|
Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option :
|
|
Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option);
|
|
}
|
|
|
|
return promiseType;
|
|
}
|
|
|
|
function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type {
|
|
if (!func.body) {
|
|
return errorType;
|
|
}
|
|
|
|
const functionFlags = getFunctionFlags(func);
|
|
const isAsync = (functionFlags & FunctionFlags.Async) !== 0;
|
|
const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0;
|
|
|
|
let returnType: Type | undefined;
|
|
let yieldType: Type | undefined;
|
|
let nextType: Type | undefined;
|
|
let fallbackReturnType: Type = voidType;
|
|
if (func.body.kind !== SyntaxKind.Block) { // Async or normal arrow function
|
|
returnType = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions);
|
|
if (isAsync) {
|
|
// From within an async function you can return either a non-promise value or a promise. Any
|
|
// Promise/A+ compatible implementation will always assimilate any foreign promise, so the
|
|
// return type of the body should be unwrapped to its awaited type, which we will wrap in
|
|
// the native Promise<T> type later in this function.
|
|
returnType = checkAwaitedType(returnType, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
|
|
}
|
|
}
|
|
else if (isGenerator) { // Generator or AsyncGenerator function
|
|
const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode);
|
|
if (!returnTypes) {
|
|
fallbackReturnType = neverType;
|
|
}
|
|
else if (returnTypes.length > 0) {
|
|
returnType = getUnionType(returnTypes, UnionReduction.Subtype);
|
|
}
|
|
const { yieldTypes, nextTypes } = checkAndAggregateYieldOperandTypes(func, checkMode);
|
|
yieldType = some(yieldTypes) ? getUnionType(yieldTypes, UnionReduction.Subtype) : undefined;
|
|
nextType = some(nextTypes) ? getIntersectionType(nextTypes) : undefined;
|
|
}
|
|
else { // Async or normal function
|
|
const types = checkAndAggregateReturnExpressionTypes(func, checkMode);
|
|
if (!types) {
|
|
// For an async function, the return type will not be never, but rather a Promise for never.
|
|
return functionFlags & FunctionFlags.Async
|
|
? createPromiseReturnType(func, neverType) // Async function
|
|
: neverType; // Normal function
|
|
}
|
|
if (types.length === 0) {
|
|
// For an async function, the return type will not be void, but rather a Promise for void.
|
|
return functionFlags & FunctionFlags.Async
|
|
? createPromiseReturnType(func, voidType) // Async function
|
|
: voidType; // Normal function
|
|
}
|
|
|
|
// Return a union of the return expression types.
|
|
returnType = getUnionType(types, UnionReduction.Subtype);
|
|
}
|
|
|
|
if (returnType || yieldType || nextType) {
|
|
const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func);
|
|
if (!contextualSignature) {
|
|
if (yieldType) reportErrorsFromWidening(func, yieldType, WideningKind.GeneratorYield);
|
|
if (returnType) reportErrorsFromWidening(func, returnType);
|
|
if (nextType) reportErrorsFromWidening(func, nextType);
|
|
}
|
|
if (returnType && isUnitType(returnType) ||
|
|
yieldType && isUnitType(yieldType) ||
|
|
nextType && isUnitType(nextType)) {
|
|
const contextualType = !contextualSignature ? undefined :
|
|
contextualSignature === getSignatureFromDeclaration(func) ? isGenerator ? undefined : returnType :
|
|
instantiateContextualType(getReturnTypeOfSignature(contextualSignature), func);
|
|
if (isGenerator) {
|
|
yieldType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(yieldType, contextualType, IterationTypeKind.Yield, isAsync);
|
|
returnType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(returnType, contextualType, IterationTypeKind.Return, isAsync);
|
|
nextType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(nextType, contextualType, IterationTypeKind.Next, isAsync);
|
|
}
|
|
else {
|
|
returnType = getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(returnType, contextualType, isAsync);
|
|
}
|
|
}
|
|
|
|
if (yieldType) yieldType = getWidenedType(yieldType);
|
|
if (returnType) returnType = getWidenedType(returnType);
|
|
if (nextType) nextType = getWidenedType(nextType);
|
|
}
|
|
|
|
if (isGenerator) {
|
|
return createGeneratorReturnType(
|
|
yieldType || neverType,
|
|
returnType || fallbackReturnType,
|
|
nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType,
|
|
isAsync);
|
|
}
|
|
else {
|
|
// From within an async function you can return either a non-promise value or a promise. Any
|
|
// Promise/A+ compatible implementation will always assimilate any foreign promise, so the
|
|
// return type of the body is awaited type of the body, wrapped in a native Promise<T> type.
|
|
return isAsync
|
|
? createPromiseType(returnType || fallbackReturnType)
|
|
: returnType || fallbackReturnType;
|
|
}
|
|
}
|
|
|
|
function createGeneratorReturnType(yieldType: Type, returnType: Type, nextType: Type, isAsyncGenerator: boolean) {
|
|
const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver;
|
|
const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false);
|
|
yieldType = resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || unknownType;
|
|
returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType;
|
|
nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType;
|
|
if (globalGeneratorType === emptyGenericType) {
|
|
// Fall back to the global IterableIterator if returnType is assignable to the expected return iteration
|
|
// type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to
|
|
// nextType.
|
|
const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false);
|
|
const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined;
|
|
const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType;
|
|
const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType;
|
|
if (isTypeAssignableTo(returnType, iterableIteratorReturnType) &&
|
|
isTypeAssignableTo(iterableIteratorNextType, nextType)) {
|
|
if (globalType !== emptyGenericType) {
|
|
return createTypeFromGenericGlobalType(globalType, [yieldType]);
|
|
}
|
|
|
|
// The global IterableIterator type doesn't exist, so report an error
|
|
resolver.getGlobalIterableIteratorType(/*reportErrors*/ true);
|
|
return emptyObjectType;
|
|
}
|
|
|
|
// The global Generator type doesn't exist, so report an error
|
|
resolver.getGlobalGeneratorType(/*reportErrors*/ true);
|
|
return emptyObjectType;
|
|
}
|
|
|
|
return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]);
|
|
}
|
|
|
|
function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) {
|
|
const yieldTypes: Type[] = [];
|
|
const nextTypes: Type[] = [];
|
|
const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0;
|
|
forEachYieldExpression(<Block>func.body, yieldExpression => {
|
|
const yieldExpressionType = yieldExpression.expression ? checkExpression(yieldExpression.expression, checkMode) : undefinedWideningType;
|
|
pushIfUnique(yieldTypes, getYieldedTypeOfYieldExpression(yieldExpression, yieldExpressionType, anyType, isAsync));
|
|
let nextType: Type | undefined;
|
|
if (yieldExpression.asteriskToken) {
|
|
const iterationTypes = getIterationTypesOfIterable(
|
|
yieldExpressionType,
|
|
isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar,
|
|
yieldExpression.expression);
|
|
nextType = iterationTypes && iterationTypes.nextType;
|
|
}
|
|
else {
|
|
nextType = getContextualType(yieldExpression);
|
|
}
|
|
if (nextType) pushIfUnique(nextTypes, nextType);
|
|
});
|
|
return { yieldTypes, nextTypes };
|
|
}
|
|
|
|
function getYieldedTypeOfYieldExpression(node: YieldExpression, expressionType: Type, sentType: Type, isAsync: boolean): Type | undefined {
|
|
const errorNode = node.expression || node;
|
|
// A `yield*` expression effectively yields everything that its operand yields
|
|
const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, expressionType, sentType, errorNode) : expressionType;
|
|
return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken
|
|
? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member
|
|
: Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
|
|
}
|
|
|
|
/**
|
|
* Collect the TypeFacts learned from a typeof switch with
|
|
* total clauses `witnesses`, and the active clause ranging
|
|
* from `start` to `end`. Parameter `hasDefault` denotes
|
|
* whether the active clause contains a default clause.
|
|
*/
|
|
function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts {
|
|
let facts: TypeFacts = TypeFacts.None;
|
|
// When in the default we only collect inequality facts
|
|
// because default is 'in theory' a set of infinite
|
|
// equalities.
|
|
if (hasDefault) {
|
|
// Value is not equal to any types after the active clause.
|
|
for (let i = end; i < witnesses.length; i++) {
|
|
facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
|
|
}
|
|
// Remove inequalities for types that appear in the
|
|
// active clause because they appear before other
|
|
// types collected so far.
|
|
for (let i = start; i < end; i++) {
|
|
facts &= ~(typeofNEFacts.get(witnesses[i]) || 0);
|
|
}
|
|
// Add inequalities for types before the active clause unconditionally.
|
|
for (let i = 0; i < start; i++) {
|
|
facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject;
|
|
}
|
|
}
|
|
// When in an active clause without default the set of
|
|
// equalities is finite.
|
|
else {
|
|
// Add equalities for all types in the active clause.
|
|
for (let i = start; i < end; i++) {
|
|
facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject;
|
|
}
|
|
// Remove equalities for types that appear before the
|
|
// active clause.
|
|
for (let i = 0; i < start; i++) {
|
|
facts &= ~(typeofEQFacts.get(witnesses[i]) || 0);
|
|
}
|
|
}
|
|
return facts;
|
|
}
|
|
|
|
function isExhaustiveSwitchStatement(node: SwitchStatement): boolean {
|
|
const links = getNodeLinks(node);
|
|
return links.isExhaustive !== undefined ? links.isExhaustive : (links.isExhaustive = computeExhaustiveSwitchStatement(node));
|
|
}
|
|
|
|
function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean {
|
|
if (node.expression.kind === SyntaxKind.TypeOfExpression) {
|
|
const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression);
|
|
const witnesses = getSwitchClauseTypeOfWitnesses(node, /*retainDefault*/ false);
|
|
// notEqualFacts states that the type of the switched value is not equal to every type in the switch.
|
|
const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true);
|
|
const type = getBaseConstraintOfType(operandType) || operandType;
|
|
return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never);
|
|
}
|
|
const type = getTypeOfExpression(node.expression);
|
|
if (!isLiteralType(type)) {
|
|
return false;
|
|
}
|
|
const switchTypes = getSwitchClauseTypes(node);
|
|
if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) {
|
|
return false;
|
|
}
|
|
return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes);
|
|
}
|
|
|
|
function functionHasImplicitReturn(func: FunctionLikeDeclaration) {
|
|
return func.endFlowNode && isReachableFlowNode(func.endFlowNode);
|
|
}
|
|
|
|
/** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */
|
|
function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined): Type[] | undefined {
|
|
const functionFlags = getFunctionFlags(func);
|
|
const aggregatedTypes: Type[] = [];
|
|
let hasReturnWithNoExpression = functionHasImplicitReturn(func);
|
|
let hasReturnOfTypeNever = false;
|
|
forEachReturnStatement(<Block>func.body, returnStatement => {
|
|
const expr = returnStatement.expression;
|
|
if (expr) {
|
|
let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions);
|
|
if (functionFlags & FunctionFlags.Async) {
|
|
// From within an async function you can return either a non-promise value or a promise. Any
|
|
// Promise/A+ compatible implementation will always assimilate any foreign promise, so the
|
|
// return type of the body should be unwrapped to its awaited type, which should be wrapped in
|
|
// the native Promise<T> type by the caller.
|
|
type = checkAwaitedType(type, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
|
|
}
|
|
if (type.flags & TypeFlags.Never) {
|
|
hasReturnOfTypeNever = true;
|
|
}
|
|
pushIfUnique(aggregatedTypes, type);
|
|
}
|
|
else {
|
|
hasReturnWithNoExpression = true;
|
|
}
|
|
});
|
|
if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) {
|
|
return undefined;
|
|
}
|
|
if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression &&
|
|
!(isJSConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) {
|
|
// Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined
|
|
pushIfUnique(aggregatedTypes, undefinedType);
|
|
}
|
|
return aggregatedTypes;
|
|
}
|
|
function mayReturnNever(func: FunctionLikeDeclaration): boolean {
|
|
switch (func.kind) {
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
return true;
|
|
case SyntaxKind.MethodDeclaration:
|
|
return func.parent.kind === SyntaxKind.ObjectLiteralExpression;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* TypeScript Specification 1.0 (6.3) - July 2014
|
|
* An explicitly typed function whose return type isn't the Void type,
|
|
* the Any type, or a union type containing the Void or Any type as a constituent
|
|
* must have at least one return statement somewhere in its body.
|
|
* An exception to this rule is if the function implementation consists of a single 'throw' statement.
|
|
*
|
|
* @param returnType - return type of the function, can be undefined if return type is not explicitly specified
|
|
*/
|
|
function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined): void {
|
|
if (!produceDiagnostics) {
|
|
return;
|
|
}
|
|
|
|
const functionFlags = getFunctionFlags(func);
|
|
const type = returnType && getReturnOrPromisedType(returnType, functionFlags);
|
|
|
|
// Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions.
|
|
if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) {
|
|
return;
|
|
}
|
|
|
|
// If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check.
|
|
// also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw
|
|
if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) {
|
|
return;
|
|
}
|
|
|
|
const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn;
|
|
|
|
if (type && type.flags & TypeFlags.Never) {
|
|
error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
|
|
}
|
|
else if (type && !hasExplicitReturn) {
|
|
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
|
|
// this function does not conform to the specification.
|
|
// NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present
|
|
error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
|
|
}
|
|
else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) {
|
|
error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined);
|
|
}
|
|
else if (compilerOptions.noImplicitReturns) {
|
|
if (!type) {
|
|
// If return type annotation is omitted check if function has any explicit return statements.
|
|
// If it does not have any - its inferred return type is void - don't do any checks.
|
|
// Otherwise get inferred return type from function body and report error only if it is not void / anytype
|
|
if (!hasExplicitReturn) {
|
|
return;
|
|
}
|
|
const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func));
|
|
if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) {
|
|
return;
|
|
}
|
|
}
|
|
error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Not_all_code_paths_return_a_value);
|
|
}
|
|
}
|
|
|
|
function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode): Type {
|
|
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
|
|
checkNodeDeferred(node);
|
|
|
|
// The identityMapper object is used to indicate that function expressions are wildcards
|
|
if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) {
|
|
// Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage
|
|
if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) {
|
|
// Return plain anyFunctionType if there is no possibility we'll make inferences from the return type
|
|
const contextualSignature = getContextualSignature(node);
|
|
if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) {
|
|
const links = getNodeLinks(node);
|
|
if (links.contextFreeType) {
|
|
return links.contextFreeType;
|
|
}
|
|
const returnType = getReturnTypeFromBody(node, checkMode);
|
|
const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
|
|
const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, undefined, undefined);
|
|
returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType;
|
|
return links.contextFreeType = returnOnlyType;
|
|
}
|
|
}
|
|
return anyFunctionType;
|
|
}
|
|
|
|
// Grammar checking
|
|
const hasGrammarError = checkGrammarFunctionLikeDeclaration(node);
|
|
if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) {
|
|
checkGrammarForGenerator(node);
|
|
}
|
|
|
|
contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode);
|
|
|
|
return getTypeOfSymbol(getSymbolOfNode(node));
|
|
}
|
|
|
|
function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) {
|
|
const links = getNodeLinks(node);
|
|
// Check if function expression is contextually typed and assign parameter types if so.
|
|
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
|
|
const contextualSignature = getContextualSignature(node);
|
|
// If a type check is started at a function expression that is an argument of a function call, obtaining the
|
|
// contextual type may recursively get back to here during overload resolution of the call. If so, we will have
|
|
// already assigned contextual types.
|
|
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
|
|
links.flags |= NodeCheckFlags.ContextChecked;
|
|
const signature = firstOrUndefined(getSignaturesOfType(getTypeOfSymbol(getSymbolOfNode(node)), SignatureKind.Call));
|
|
if (!signature) {
|
|
return;
|
|
}
|
|
if (isContextSensitive(node)) {
|
|
if (contextualSignature) {
|
|
const inferenceContext = getInferenceContext(node);
|
|
if (checkMode && checkMode & CheckMode.Inferential) {
|
|
inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!);
|
|
}
|
|
const instantiatedContextualSignature = inferenceContext ?
|
|
instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature;
|
|
assignContextualParameterTypes(signature, instantiatedContextualSignature);
|
|
}
|
|
else {
|
|
// Force resolution of all parameter types such that the absence of a contextual type is consistently reflected.
|
|
assignNonContextualParameterTypes(signature);
|
|
}
|
|
}
|
|
if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) {
|
|
const returnType = getReturnTypeFromBody(node, checkMode);
|
|
if (!signature.resolvedReturnType) {
|
|
signature.resolvedReturnType = returnType;
|
|
}
|
|
}
|
|
checkSignatureDeclaration(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getReturnOrPromisedType(type: Type | undefined, functionFlags: FunctionFlags) {
|
|
const isGenerator = !!(functionFlags & FunctionFlags.Generator);
|
|
const isAsync = !!(functionFlags & FunctionFlags.Async);
|
|
return type && isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsync) || errorType :
|
|
type && isAsync ? getAwaitedType(type) || errorType :
|
|
type;
|
|
}
|
|
|
|
function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) {
|
|
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
|
|
|
|
const functionFlags = getFunctionFlags(node);
|
|
const returnType = getReturnTypeFromAnnotation(node);
|
|
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType);
|
|
|
|
if (node.body) {
|
|
if (!getEffectiveReturnTypeNode(node)) {
|
|
// There are some checks that are only performed in getReturnTypeFromBody, that may produce errors
|
|
// we need. An example is the noImplicitAny errors resulting from widening the return expression
|
|
// of a function. Because checking of function expression bodies is deferred, there was never an
|
|
// appropriate time to do this during the main walk of the file (see the comment at the top of
|
|
// checkFunctionExpressionBodies). So it must be done now.
|
|
getReturnTypeOfSignature(getSignatureFromDeclaration(node));
|
|
}
|
|
|
|
if (node.body.kind === SyntaxKind.Block) {
|
|
checkSourceElement(node.body);
|
|
}
|
|
else {
|
|
// From within an async function you can return either a non-promise value or a promise. Any
|
|
// Promise/A+ compatible implementation will always assimilate any foreign promise, so we
|
|
// should not be checking assignability of a promise to the return type. Instead, we need to
|
|
// check assignability of the awaited type of the expression body against the promised type of
|
|
// its return type annotation.
|
|
const exprType = checkExpression(node.body);
|
|
const returnOrPromisedType = getReturnOrPromisedType(returnType, functionFlags);
|
|
if (returnOrPromisedType) {
|
|
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function
|
|
const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
|
|
checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body);
|
|
}
|
|
else { // Normal function
|
|
checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, isAwaitValid = false): boolean {
|
|
if (!isTypeAssignableTo(type, numberOrBigIntType)) {
|
|
const awaitedType = isAwaitValid && getAwaitedTypeOfPromise(type);
|
|
errorAndMaybeSuggestAwait(
|
|
operand,
|
|
!!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType),
|
|
diagnostic);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function isReadonlyAssignmentDeclaration(d: Declaration) {
|
|
if (!isCallExpression(d)) {
|
|
return false;
|
|
}
|
|
if (!isBindableObjectDefinePropertyCall(d)) {
|
|
return false;
|
|
}
|
|
const objectLitType = checkExpressionCached(d.arguments[2]);
|
|
const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String);
|
|
if (valueType) {
|
|
const writableProp = getPropertyOfType(objectLitType, "writable" as __String);
|
|
const writableType = writableProp && getTypeOfSymbol(writableProp);
|
|
if (!writableType || writableType === falseType || writableType === regularFalseType) {
|
|
return true;
|
|
}
|
|
// We include this definition whereupon we walk back and check the type at the declaration because
|
|
// The usual definition of `Object.defineProperty` will _not_ cause literal types to be preserved in the
|
|
// argument types, should the type be contextualized by the call itself.
|
|
if (writableProp && writableProp.valueDeclaration && isPropertyAssignment(writableProp.valueDeclaration)) {
|
|
const initializer = writableProp.valueDeclaration.initializer;
|
|
const rawOriginalType = checkExpression(initializer);
|
|
if (rawOriginalType === falseType || rawOriginalType === regularFalseType) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
const setProp = getPropertyOfType(objectLitType, "set" as __String);
|
|
return !setProp;
|
|
}
|
|
|
|
function isReadonlySymbol(symbol: Symbol): boolean {
|
|
// The following symbols are considered read-only:
|
|
// Properties with a 'readonly' modifier
|
|
// Variables declared with 'const'
|
|
// Get accessors without matching set accessors
|
|
// Enum members
|
|
// Object.defineProperty assignments with writable false or no setter
|
|
// Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation)
|
|
return !!(getCheckFlags(symbol) & CheckFlags.Readonly ||
|
|
symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly ||
|
|
symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const ||
|
|
symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) ||
|
|
symbol.flags & SymbolFlags.EnumMember ||
|
|
some(symbol.declarations, isReadonlyAssignmentDeclaration)
|
|
);
|
|
}
|
|
|
|
function isAssignmentToReadonlyEntity(expr: Expression, symbol: Symbol, assignmentKind: AssignmentKind) {
|
|
if (assignmentKind === AssignmentKind.None) {
|
|
// no assigment means it doesn't matter whether the entity is readonly
|
|
return false;
|
|
}
|
|
if (isReadonlySymbol(symbol)) {
|
|
// Allow assignments to readonly properties within constructors of the same class declaration.
|
|
if (symbol.flags & SymbolFlags.Property &&
|
|
isAccessExpression(expr) &&
|
|
expr.expression.kind === SyntaxKind.ThisKeyword) {
|
|
// Look for if this is the constructor for the class that `symbol` is a property of.
|
|
const ctor = getContainingFunction(expr);
|
|
if (!(ctor && ctor.kind === SyntaxKind.Constructor)) {
|
|
return true;
|
|
}
|
|
if (symbol.valueDeclaration) {
|
|
const isAssignmentDeclaration = isBinaryExpression(symbol.valueDeclaration);
|
|
const isLocalPropertyDeclaration = ctor.parent === symbol.valueDeclaration.parent;
|
|
const isLocalParameterProperty = ctor === symbol.valueDeclaration.parent;
|
|
const isLocalThisPropertyAssignment = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor.parent;
|
|
const isLocalThisPropertyAssignmentConstructorFunction = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor;
|
|
const isWriteableSymbol =
|
|
isLocalPropertyDeclaration
|
|
|| isLocalParameterProperty
|
|
|| isLocalThisPropertyAssignment
|
|
|| isLocalThisPropertyAssignmentConstructorFunction;
|
|
return !isWriteableSymbol;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (isAccessExpression(expr)) {
|
|
// references through namespace import should be readonly
|
|
const node = skipParentheses(expr.expression);
|
|
if (node.kind === SyntaxKind.Identifier) {
|
|
const symbol = getNodeLinks(node).resolvedSymbol!;
|
|
if (symbol.flags & SymbolFlags.Alias) {
|
|
const declaration = getDeclarationOfAliasSymbol(symbol);
|
|
return !!declaration && declaration.kind === SyntaxKind.NamespaceImport;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean {
|
|
// References are combinations of identifiers, parentheses, and property accesses.
|
|
const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses);
|
|
if (node.kind !== SyntaxKind.Identifier && !isAccessExpression(node)) {
|
|
error(expr, invalidReferenceMessage);
|
|
return false;
|
|
}
|
|
if (node.flags & NodeFlags.OptionalChain) {
|
|
error(expr, invalidOptionalChainMessage);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function checkDeleteExpression(node: DeleteExpression): Type {
|
|
checkExpression(node.expression);
|
|
const expr = skipParentheses(node.expression);
|
|
if (!isAccessExpression(expr)) {
|
|
error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference);
|
|
return booleanType;
|
|
}
|
|
if (expr.kind === SyntaxKind.PropertyAccessExpression && isPrivateIdentifier((expr as PropertyAccessExpression).name)) {
|
|
error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier);
|
|
}
|
|
const links = getNodeLinks(expr);
|
|
const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol);
|
|
if (symbol && isReadonlySymbol(symbol)) {
|
|
error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property);
|
|
}
|
|
return booleanType;
|
|
}
|
|
|
|
function checkTypeOfExpression(node: TypeOfExpression): Type {
|
|
checkExpression(node.expression);
|
|
return typeofType;
|
|
}
|
|
|
|
function checkVoidExpression(node: VoidExpression): Type {
|
|
checkExpression(node.expression);
|
|
return undefinedWideningType;
|
|
}
|
|
|
|
function isTopLevelAwait(node: AwaitExpression) {
|
|
const container = getThisContainer(node, /*includeArrowFunctions*/ true);
|
|
return isSourceFile(container);
|
|
}
|
|
|
|
function checkAwaitExpression(node: AwaitExpression): Type {
|
|
// Grammar checking
|
|
if (produceDiagnostics) {
|
|
if (!(node.flags & NodeFlags.AwaitContext)) {
|
|
if (isTopLevelAwait(node)) {
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
if (!hasParseDiagnostics(sourceFile)) {
|
|
let span: TextSpan | undefined;
|
|
if (!isEffectiveExternalModule(sourceFile, compilerOptions)) {
|
|
if (!span) span = getSpanOfTokenAtPosition(sourceFile, node.pos);
|
|
const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length,
|
|
Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module);
|
|
diagnostics.add(diagnostic);
|
|
}
|
|
if ((moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) || languageVersion < ScriptTarget.ES2017) {
|
|
span = getSpanOfTokenAtPosition(sourceFile, node.pos);
|
|
const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length,
|
|
Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_esnext_or_system_and_the_target_option_is_set_to_es2017_or_higher);
|
|
diagnostics.add(diagnostic);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// use of 'await' in non-async function
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
if (!hasParseDiagnostics(sourceFile)) {
|
|
const span = getSpanOfTokenAtPosition(sourceFile, node.pos);
|
|
const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules);
|
|
const func = getContainingFunction(node);
|
|
if (func && func.kind !== SyntaxKind.Constructor && (getFunctionFlags(func) & FunctionFlags.Async) === 0) {
|
|
const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
|
|
addRelatedInfo(diagnostic, relatedInfo);
|
|
}
|
|
diagnostics.add(diagnostic);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isInParameterInitializerBeforeContainingFunction(node)) {
|
|
error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer);
|
|
}
|
|
}
|
|
|
|
const operandType = checkExpression(node.expression);
|
|
const awaitedType = checkAwaitedType(operandType, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
|
|
if (awaitedType === operandType && awaitedType !== errorType && !(operandType.flags & TypeFlags.AnyOrUnknown)) {
|
|
addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression));
|
|
}
|
|
return awaitedType;
|
|
}
|
|
|
|
function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type {
|
|
const operandType = checkExpression(node.operand);
|
|
if (operandType === silentNeverType) {
|
|
return silentNeverType;
|
|
}
|
|
switch (node.operand.kind) {
|
|
case SyntaxKind.NumericLiteral:
|
|
switch (node.operator) {
|
|
case SyntaxKind.MinusToken:
|
|
return getFreshTypeOfLiteralType(getLiteralType(-(node.operand as NumericLiteral).text));
|
|
case SyntaxKind.PlusToken:
|
|
return getFreshTypeOfLiteralType(getLiteralType(+(node.operand as NumericLiteral).text));
|
|
}
|
|
break;
|
|
case SyntaxKind.BigIntLiteral:
|
|
if (node.operator === SyntaxKind.MinusToken) {
|
|
return getFreshTypeOfLiteralType(getLiteralType({
|
|
negative: true,
|
|
base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text)
|
|
}));
|
|
}
|
|
}
|
|
switch (node.operator) {
|
|
case SyntaxKind.PlusToken:
|
|
case SyntaxKind.MinusToken:
|
|
case SyntaxKind.TildeToken:
|
|
checkNonNullType(operandType, node.operand);
|
|
if (maybeTypeOfKind(operandType, TypeFlags.ESSymbolLike)) {
|
|
error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator));
|
|
}
|
|
if (node.operator === SyntaxKind.PlusToken) {
|
|
if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) {
|
|
error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType)));
|
|
}
|
|
return numberType;
|
|
}
|
|
return getUnaryResultType(operandType);
|
|
case SyntaxKind.ExclamationToken:
|
|
checkTruthinessExpression(node.operand);
|
|
const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy);
|
|
return facts === TypeFacts.Truthy ? falseType :
|
|
facts === TypeFacts.Falsy ? trueType :
|
|
booleanType;
|
|
case SyntaxKind.PlusPlusToken:
|
|
case SyntaxKind.MinusMinusToken:
|
|
const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand),
|
|
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type);
|
|
if (ok) {
|
|
// run check only if former checks succeeded to avoid reporting cascading errors
|
|
checkReferenceExpression(
|
|
node.operand,
|
|
Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access,
|
|
Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access);
|
|
}
|
|
return getUnaryResultType(operandType);
|
|
}
|
|
return errorType;
|
|
}
|
|
|
|
function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type {
|
|
const operandType = checkExpression(node.operand);
|
|
if (operandType === silentNeverType) {
|
|
return silentNeverType;
|
|
}
|
|
const ok = checkArithmeticOperandType(
|
|
node.operand,
|
|
checkNonNullType(operandType, node.operand),
|
|
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type);
|
|
if (ok) {
|
|
// run check only if former checks succeeded to avoid reporting cascading errors
|
|
checkReferenceExpression(
|
|
node.operand,
|
|
Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access,
|
|
Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access);
|
|
}
|
|
return getUnaryResultType(operandType);
|
|
}
|
|
|
|
function getUnaryResultType(operandType: Type): Type {
|
|
if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) {
|
|
return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike)
|
|
? numberOrBigIntType
|
|
: bigintType;
|
|
}
|
|
// If it's not a bigint type, implicit coercion will result in a number
|
|
return numberType;
|
|
}
|
|
|
|
// Return true if type might be of the given kind. A union or intersection type might be of a given
|
|
// kind if at least one constituent type is of the given kind.
|
|
function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean {
|
|
if (type.flags & kind) {
|
|
return true;
|
|
}
|
|
if (type.flags & TypeFlags.UnionOrIntersection) {
|
|
const types = (<UnionOrIntersectionType>type).types;
|
|
for (const t of types) {
|
|
if (maybeTypeOfKind(t, kind)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean {
|
|
if (source.flags & kind) {
|
|
return true;
|
|
}
|
|
if (strict && source.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) {
|
|
return false;
|
|
}
|
|
return !!(kind & TypeFlags.NumberLike) && isTypeAssignableTo(source, numberType) ||
|
|
!!(kind & TypeFlags.BigIntLike) && isTypeAssignableTo(source, bigintType) ||
|
|
!!(kind & TypeFlags.StringLike) && isTypeAssignableTo(source, stringType) ||
|
|
!!(kind & TypeFlags.BooleanLike) && isTypeAssignableTo(source, booleanType) ||
|
|
!!(kind & TypeFlags.Void) && isTypeAssignableTo(source, voidType) ||
|
|
!!(kind & TypeFlags.Never) && isTypeAssignableTo(source, neverType) ||
|
|
!!(kind & TypeFlags.Null) && isTypeAssignableTo(source, nullType) ||
|
|
!!(kind & TypeFlags.Undefined) && isTypeAssignableTo(source, undefinedType) ||
|
|
!!(kind & TypeFlags.ESSymbol) && isTypeAssignableTo(source, esSymbolType) ||
|
|
!!(kind & TypeFlags.NonPrimitive) && isTypeAssignableTo(source, nonPrimitiveType);
|
|
}
|
|
|
|
function allTypesAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean {
|
|
return source.flags & TypeFlags.Union ?
|
|
every((source as UnionType).types, subType => allTypesAssignableToKind(subType, kind, strict)) :
|
|
isTypeAssignableToKind(source, kind, strict);
|
|
}
|
|
|
|
function isConstEnumObjectType(type: Type): boolean {
|
|
return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol);
|
|
}
|
|
|
|
function isConstEnumSymbol(symbol: Symbol): boolean {
|
|
return (symbol.flags & SymbolFlags.ConstEnum) !== 0;
|
|
}
|
|
|
|
function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
|
|
if (leftType === silentNeverType || rightType === silentNeverType) {
|
|
return silentNeverType;
|
|
}
|
|
// TypeScript 1.0 spec (April 2014): 4.15.4
|
|
// The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type,
|
|
// and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature.
|
|
// The result is always of the Boolean primitive type.
|
|
// NOTE: do not raise error if leftType is unknown as related error was already reported
|
|
if (!isTypeAny(leftType) &&
|
|
allTypesAssignableToKind(leftType, TypeFlags.Primitive)) {
|
|
error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
|
|
}
|
|
// NOTE: do not raise error if right is unknown as related error was already reported
|
|
if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) {
|
|
error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type);
|
|
}
|
|
return booleanType;
|
|
}
|
|
|
|
function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
|
|
if (leftType === silentNeverType || rightType === silentNeverType) {
|
|
return silentNeverType;
|
|
}
|
|
leftType = checkNonNullType(leftType, left);
|
|
rightType = checkNonNullType(rightType, right);
|
|
// TypeScript 1.0 spec (April 2014): 4.15.5
|
|
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
|
|
// and the right operand to be of type Any, an object type, or a type parameter type.
|
|
// The result is always of the Boolean primitive type.
|
|
if (!(isTypeComparableTo(leftType, stringType) || isTypeAssignableToKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbolLike))) {
|
|
error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol);
|
|
}
|
|
if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) {
|
|
error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
|
|
}
|
|
return booleanType;
|
|
}
|
|
|
|
function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type {
|
|
const properties = node.properties;
|
|
if (strictNullChecks && properties.length === 0) {
|
|
return checkNonNullType(sourceType, node);
|
|
}
|
|
for (let i = 0; i < properties.length; i++) {
|
|
checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis);
|
|
}
|
|
return sourceType;
|
|
}
|
|
|
|
/** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */
|
|
function checkObjectLiteralDestructuringPropertyAssignment(node: ObjectLiteralExpression, objectLiteralType: Type, propertyIndex: number, allProperties?: NodeArray<ObjectLiteralElementLike>, rightIsThis = false) {
|
|
const properties = node.properties;
|
|
const property = properties[propertyIndex];
|
|
if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
const name = property.name;
|
|
const exprType = getLiteralTypeFromPropertyName(name);
|
|
if (isTypeUsableAsPropertyName(exprType)) {
|
|
const text = getPropertyNameFromType(exprType);
|
|
const prop = getPropertyOfType(objectLiteralType, text);
|
|
if (prop) {
|
|
markPropertyAsReferenced(prop, property, rightIsThis);
|
|
checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop);
|
|
}
|
|
}
|
|
const elementType = getIndexedAccessType(objectLiteralType, exprType, name);
|
|
const type = getFlowTypeOfDestructuring(property, elementType);
|
|
return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type);
|
|
}
|
|
else if (property.kind === SyntaxKind.SpreadAssignment) {
|
|
if (propertyIndex < properties.length - 1) {
|
|
error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
|
|
}
|
|
else {
|
|
if (languageVersion < ScriptTarget.ESNext) {
|
|
checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest);
|
|
}
|
|
const nonRestNames: PropertyName[] = [];
|
|
if (allProperties) {
|
|
for (const otherProperty of allProperties) {
|
|
if (!isSpreadAssignment(otherProperty)) {
|
|
nonRestNames.push(otherProperty.name);
|
|
}
|
|
}
|
|
}
|
|
const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol);
|
|
checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
|
|
return checkDestructuringAssignment(property.expression, type);
|
|
}
|
|
}
|
|
else {
|
|
error(property, Diagnostics.Property_assignment_expected);
|
|
}
|
|
}
|
|
|
|
function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type {
|
|
const elements = node.elements;
|
|
if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.Read);
|
|
}
|
|
// This elementType will be used if the specific property corresponding to this index is not
|
|
// present (aka the tuple element property). This call also checks that the parentType is in
|
|
// fact an iterable or array (depending on target language).
|
|
const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, sourceType, undefinedType, node) || errorType;
|
|
for (let i = 0; i < elements.length; i++) {
|
|
checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, elementType, checkMode);
|
|
}
|
|
return sourceType;
|
|
}
|
|
|
|
function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: Type,
|
|
elementIndex: number, elementType: Type, checkMode?: CheckMode) {
|
|
const elements = node.elements;
|
|
const element = elements[elementIndex];
|
|
if (element.kind !== SyntaxKind.OmittedExpression) {
|
|
if (element.kind !== SyntaxKind.SpreadElement) {
|
|
const indexType = getLiteralType(elementIndex);
|
|
if (isArrayLikeType(sourceType)) {
|
|
// We create a synthetic expression so that getIndexedAccessType doesn't get confused
|
|
// when the element is a SyntaxKind.ElementAccessExpression.
|
|
const accessFlags = hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0;
|
|
const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, createSyntheticExpression(element, indexType), accessFlags) || errorType;
|
|
const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType;
|
|
const type = getFlowTypeOfDestructuring(element, assignedType);
|
|
return checkDestructuringAssignment(element, type, checkMode);
|
|
}
|
|
return checkDestructuringAssignment(element, elementType, checkMode);
|
|
}
|
|
if (elementIndex < elements.length - 1) {
|
|
error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
|
|
}
|
|
else {
|
|
const restExpression = (<SpreadElement>element).expression;
|
|
if (restExpression.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>restExpression).operatorToken.kind === SyntaxKind.EqualsToken) {
|
|
error((<BinaryExpression>restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer);
|
|
}
|
|
else {
|
|
checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
|
|
const type = everyType(sourceType, isTupleType) ?
|
|
mapType(sourceType, t => sliceTupleType(<TupleTypeReference>t, elementIndex)) :
|
|
createArrayType(elementType);
|
|
return checkDestructuringAssignment(restExpression, type, checkMode);
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type {
|
|
let target: Expression;
|
|
if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
const prop = <ShorthandPropertyAssignment>exprOrAssignment;
|
|
if (prop.objectAssignmentInitializer) {
|
|
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
|
|
// undefined from the final type.
|
|
if (strictNullChecks &&
|
|
!(getFalsyFlags(checkExpression(prop.objectAssignmentInitializer)) & TypeFlags.Undefined)) {
|
|
sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined);
|
|
}
|
|
checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode);
|
|
}
|
|
target = (<ShorthandPropertyAssignment>exprOrAssignment).name;
|
|
}
|
|
else {
|
|
target = exprOrAssignment;
|
|
}
|
|
|
|
if (target.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>target).operatorToken.kind === SyntaxKind.EqualsToken) {
|
|
checkBinaryExpression(<BinaryExpression>target, checkMode);
|
|
target = (<BinaryExpression>target).left;
|
|
}
|
|
if (target.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
return checkObjectLiteralAssignment(<ObjectLiteralExpression>target, sourceType, rightIsThis);
|
|
}
|
|
if (target.kind === SyntaxKind.ArrayLiteralExpression) {
|
|
return checkArrayLiteralAssignment(<ArrayLiteralExpression>target, sourceType, checkMode);
|
|
}
|
|
return checkReferenceAssignment(target, sourceType, checkMode);
|
|
}
|
|
|
|
function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode): Type {
|
|
const targetType = checkExpression(target, checkMode);
|
|
const error = target.parent.kind === SyntaxKind.SpreadAssignment ?
|
|
Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access :
|
|
Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access;
|
|
const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ?
|
|
Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access :
|
|
Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access;
|
|
if (checkReferenceExpression(target, error, optionalError)) {
|
|
checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target);
|
|
}
|
|
if (isPrivateIdentifierPropertyAccessExpression(target)) {
|
|
checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet);
|
|
}
|
|
return sourceType;
|
|
}
|
|
|
|
/**
|
|
* This is a *shallow* check: An expression is side-effect-free if the
|
|
* evaluation of the expression *itself* cannot produce side effects.
|
|
* For example, x++ / 3 is side-effect free because the / operator
|
|
* does not have side effects.
|
|
* The intent is to "smell test" an expression for correctness in positions where
|
|
* its value is discarded (e.g. the left side of the comma operator).
|
|
*/
|
|
function isSideEffectFree(node: Node): boolean {
|
|
node = skipParentheses(node);
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.RegularExpressionLiteral:
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
case SyntaxKind.TemplateExpression:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.BigIntLiteral:
|
|
case SyntaxKind.TrueKeyword:
|
|
case SyntaxKind.FalseKeyword:
|
|
case SyntaxKind.NullKeyword:
|
|
case SyntaxKind.UndefinedKeyword:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
case SyntaxKind.TypeOfExpression:
|
|
case SyntaxKind.NonNullExpression:
|
|
case SyntaxKind.JsxSelfClosingElement:
|
|
case SyntaxKind.JsxElement:
|
|
return true;
|
|
|
|
case SyntaxKind.ConditionalExpression:
|
|
return isSideEffectFree((node as ConditionalExpression).whenTrue) &&
|
|
isSideEffectFree((node as ConditionalExpression).whenFalse);
|
|
|
|
case SyntaxKind.BinaryExpression:
|
|
if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) {
|
|
return false;
|
|
}
|
|
return isSideEffectFree((node as BinaryExpression).left) &&
|
|
isSideEffectFree((node as BinaryExpression).right);
|
|
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
case SyntaxKind.PostfixUnaryExpression:
|
|
// Unary operators ~, !, +, and - have no side effects.
|
|
// The rest do.
|
|
switch ((node as PrefixUnaryExpression).operator) {
|
|
case SyntaxKind.ExclamationToken:
|
|
case SyntaxKind.PlusToken:
|
|
case SyntaxKind.MinusToken:
|
|
case SyntaxKind.TildeToken:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
// Some forms listed here for clarity
|
|
case SyntaxKind.VoidExpression: // Explicit opt-out
|
|
case SyntaxKind.TypeAssertionExpression: // Not SEF, but can produce useful type warnings
|
|
case SyntaxKind.AsExpression: // Not SEF, but can produce useful type warnings
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isTypeEqualityComparableTo(source: Type, target: Type) {
|
|
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
|
|
}
|
|
|
|
const enum CheckBinaryExpressionState {
|
|
MaybeCheckLeft,
|
|
CheckRight,
|
|
FinishCheck
|
|
}
|
|
|
|
function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
|
|
const workStacks: {
|
|
expr: BinaryExpression[],
|
|
state: CheckBinaryExpressionState[],
|
|
leftType: (Type | undefined)[]
|
|
} = {
|
|
expr: [node],
|
|
state: [CheckBinaryExpressionState.MaybeCheckLeft],
|
|
leftType: [undefined]
|
|
};
|
|
let stackIndex = 0;
|
|
let lastResult: Type | undefined;
|
|
while (stackIndex >= 0) {
|
|
node = workStacks.expr[stackIndex];
|
|
switch (workStacks.state[stackIndex]) {
|
|
case CheckBinaryExpressionState.MaybeCheckLeft: {
|
|
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
|
|
finishInvocation(checkExpression(node.right, checkMode));
|
|
break;
|
|
}
|
|
checkGrammarNullishCoalesceWithLogicalExpression(node);
|
|
const operator = node.operatorToken.kind;
|
|
if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
|
|
finishInvocation(checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
|
|
break;
|
|
}
|
|
advanceState(CheckBinaryExpressionState.CheckRight);
|
|
maybeCheckExpression(node.left);
|
|
break;
|
|
}
|
|
case CheckBinaryExpressionState.CheckRight: {
|
|
const leftType = lastResult!;
|
|
workStacks.leftType[stackIndex] = leftType;
|
|
const operator = node.operatorToken.kind;
|
|
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
|
|
checkTruthinessOfType(leftType, node.left);
|
|
}
|
|
advanceState(CheckBinaryExpressionState.FinishCheck);
|
|
maybeCheckExpression(node.right);
|
|
break;
|
|
}
|
|
case CheckBinaryExpressionState.FinishCheck: {
|
|
const leftType = workStacks.leftType[stackIndex]!;
|
|
const rightType = lastResult!;
|
|
finishInvocation(checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node));
|
|
break;
|
|
}
|
|
default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`);
|
|
}
|
|
}
|
|
|
|
return lastResult!;
|
|
|
|
function finishInvocation(result: Type) {
|
|
lastResult = result;
|
|
stackIndex--;
|
|
}
|
|
|
|
/**
|
|
* Note that `advanceState` sets the _current_ head state, and that `maybeCheckExpression` potentially pushes on a new
|
|
* head state; so `advanceState` must be called before any `maybeCheckExpression` during a state's execution.
|
|
*/
|
|
function advanceState(nextState: CheckBinaryExpressionState) {
|
|
workStacks.state[stackIndex] = nextState;
|
|
}
|
|
|
|
function maybeCheckExpression(node: Expression) {
|
|
if (isBinaryExpression(node)) {
|
|
stackIndex++;
|
|
workStacks.expr[stackIndex] = node;
|
|
workStacks.state[stackIndex] = CheckBinaryExpressionState.MaybeCheckLeft;
|
|
workStacks.leftType[stackIndex] = undefined;
|
|
}
|
|
else {
|
|
lastResult = checkExpression(node, checkMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) {
|
|
const { left, operatorToken, right } = node;
|
|
if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) {
|
|
if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) {
|
|
grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind));
|
|
}
|
|
if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) {
|
|
grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note that this and `checkBinaryExpression` above should behave mostly the same, except this elides some
|
|
// expression-wide checks and does not use a work stack to fold nested binary expressions into the same callstack frame
|
|
function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type {
|
|
const operator = operatorToken.kind;
|
|
if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) {
|
|
return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword);
|
|
}
|
|
let leftType: Type;
|
|
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
|
|
leftType = checkTruthinessExpression(left, checkMode);
|
|
}
|
|
else {
|
|
leftType = checkExpression(left, checkMode);
|
|
}
|
|
|
|
const rightType = checkExpression(right, checkMode);
|
|
return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode);
|
|
}
|
|
|
|
function checkBinaryLikeExpressionWorker(
|
|
left: Expression,
|
|
operatorToken: Node,
|
|
right: Expression,
|
|
leftType: Type,
|
|
rightType: Type,
|
|
errorNode?: Node
|
|
): Type {
|
|
const operator = operatorToken.kind;
|
|
switch (operator) {
|
|
case SyntaxKind.AsteriskToken:
|
|
case SyntaxKind.AsteriskAsteriskToken:
|
|
case SyntaxKind.AsteriskEqualsToken:
|
|
case SyntaxKind.AsteriskAsteriskEqualsToken:
|
|
case SyntaxKind.SlashToken:
|
|
case SyntaxKind.SlashEqualsToken:
|
|
case SyntaxKind.PercentToken:
|
|
case SyntaxKind.PercentEqualsToken:
|
|
case SyntaxKind.MinusToken:
|
|
case SyntaxKind.MinusEqualsToken:
|
|
case SyntaxKind.LessThanLessThanToken:
|
|
case SyntaxKind.LessThanLessThanEqualsToken:
|
|
case SyntaxKind.GreaterThanGreaterThanToken:
|
|
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
|
|
case SyntaxKind.BarToken:
|
|
case SyntaxKind.BarEqualsToken:
|
|
case SyntaxKind.CaretToken:
|
|
case SyntaxKind.CaretEqualsToken:
|
|
case SyntaxKind.AmpersandToken:
|
|
case SyntaxKind.AmpersandEqualsToken:
|
|
if (leftType === silentNeverType || rightType === silentNeverType) {
|
|
return silentNeverType;
|
|
}
|
|
|
|
leftType = checkNonNullType(leftType, left);
|
|
rightType = checkNonNullType(rightType, right);
|
|
|
|
let suggestedOperator: SyntaxKind | undefined;
|
|
// if a user tries to apply a bitwise operator to 2 boolean operands
|
|
// try and return them a helpful suggestion
|
|
if ((leftType.flags & TypeFlags.BooleanLike) &&
|
|
(rightType.flags & TypeFlags.BooleanLike) &&
|
|
(suggestedOperator = getSuggestedBooleanOperator(operatorToken.kind)) !== undefined) {
|
|
error(errorNode || operatorToken, Diagnostics.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead, tokenToString(operatorToken.kind), tokenToString(suggestedOperator));
|
|
return numberType;
|
|
}
|
|
else {
|
|
// otherwise just check each operand separately and report errors as normal
|
|
const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true);
|
|
const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true);
|
|
let resultType: Type;
|
|
// If both are any or unknown, allow operation; assume it will resolve to number
|
|
if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) ||
|
|
// Or, if neither could be bigint, implicit coercion results in a number result
|
|
!(maybeTypeOfKind(leftType, TypeFlags.BigIntLike) || maybeTypeOfKind(rightType, TypeFlags.BigIntLike))
|
|
) {
|
|
resultType = numberType;
|
|
}
|
|
// At least one is assignable to bigint, so check that both are
|
|
else if (bothAreBigIntLike(leftType, rightType)) {
|
|
switch (operator) {
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
|
|
reportOperatorError();
|
|
}
|
|
resultType = bigintType;
|
|
}
|
|
// Exactly one of leftType/rightType is assignable to bigint
|
|
else {
|
|
reportOperatorError(bothAreBigIntLike);
|
|
resultType = errorType;
|
|
}
|
|
if (leftOk && rightOk) {
|
|
checkAssignmentOperator(resultType);
|
|
}
|
|
return resultType;
|
|
}
|
|
case SyntaxKind.PlusToken:
|
|
case SyntaxKind.PlusEqualsToken:
|
|
if (leftType === silentNeverType || rightType === silentNeverType) {
|
|
return silentNeverType;
|
|
}
|
|
|
|
if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) {
|
|
leftType = checkNonNullType(leftType, left);
|
|
rightType = checkNonNullType(rightType, right);
|
|
}
|
|
|
|
let resultType: Type | undefined;
|
|
if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) {
|
|
// Operands of an enum type are treated as having the primitive type Number.
|
|
// If both operands are of the Number primitive type, the result is of the Number primitive type.
|
|
resultType = numberType;
|
|
}
|
|
else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike, /*strict*/ true)) {
|
|
// If both operands are of the BigInt primitive type, the result is of the BigInt primitive type.
|
|
resultType = bigintType;
|
|
}
|
|
else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) {
|
|
// If one or both operands are of the String primitive type, the result is of the String primitive type.
|
|
resultType = stringType;
|
|
}
|
|
else if (isTypeAny(leftType) || isTypeAny(rightType)) {
|
|
// Otherwise, the result is of type Any.
|
|
// NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we.
|
|
resultType = leftType === errorType || rightType === errorType ? errorType : anyType;
|
|
}
|
|
|
|
// Symbols are not allowed at all in arithmetic expressions
|
|
if (resultType && !checkForDisallowedESSymbolOperand(operator)) {
|
|
return resultType;
|
|
}
|
|
|
|
if (!resultType) {
|
|
// Types that have a reasonably good chance of being a valid operand type.
|
|
// If both types have an awaited type of one of these, we'll assume the user
|
|
// might be missing an await without doing an exhaustive check that inserting
|
|
// await(s) will actually be a completely valid binary expression.
|
|
const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown;
|
|
reportOperatorError((left, right) =>
|
|
isTypeAssignableToKind(left, closeEnoughKind) &&
|
|
isTypeAssignableToKind(right, closeEnoughKind));
|
|
return anyType;
|
|
}
|
|
|
|
if (operator === SyntaxKind.PlusEqualsToken) {
|
|
checkAssignmentOperator(resultType);
|
|
}
|
|
return resultType;
|
|
case SyntaxKind.LessThanToken:
|
|
case SyntaxKind.GreaterThanToken:
|
|
case SyntaxKind.LessThanEqualsToken:
|
|
case SyntaxKind.GreaterThanEqualsToken:
|
|
if (checkForDisallowedESSymbolOperand(operator)) {
|
|
leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left));
|
|
rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right));
|
|
reportOperatorErrorUnless((left, right) =>
|
|
isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || (
|
|
isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType)));
|
|
}
|
|
return booleanType;
|
|
case SyntaxKind.EqualsEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsToken:
|
|
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
|
|
return booleanType;
|
|
|
|
case SyntaxKind.InstanceOfKeyword:
|
|
return checkInstanceOfExpression(left, right, leftType, rightType);
|
|
case SyntaxKind.InKeyword:
|
|
return checkInExpression(left, right, leftType, rightType);
|
|
case SyntaxKind.AmpersandAmpersandToken:
|
|
return getTypeFacts(leftType) & TypeFacts.Truthy ?
|
|
getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) :
|
|
leftType;
|
|
case SyntaxKind.BarBarToken:
|
|
return getTypeFacts(leftType) & TypeFacts.Falsy ?
|
|
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) :
|
|
leftType;
|
|
case SyntaxKind.QuestionQuestionToken:
|
|
return getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ?
|
|
getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) :
|
|
leftType;
|
|
case SyntaxKind.EqualsToken:
|
|
const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None;
|
|
checkAssignmentDeclaration(declKind, rightType);
|
|
if (isAssignmentDeclaration(declKind)) {
|
|
if (!(rightType.flags & TypeFlags.Object) ||
|
|
declKind !== AssignmentDeclarationKind.ModuleExports &&
|
|
declKind !== AssignmentDeclarationKind.Prototype &&
|
|
!isEmptyObjectType(rightType) &&
|
|
!isFunctionObjectType(rightType as ObjectType) &&
|
|
!(getObjectFlags(rightType) & ObjectFlags.Class)) {
|
|
// don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete
|
|
checkAssignmentOperator(rightType);
|
|
}
|
|
return leftType;
|
|
}
|
|
else {
|
|
checkAssignmentOperator(rightType);
|
|
return getRegularTypeOfObjectLiteral(rightType);
|
|
}
|
|
case SyntaxKind.CommaToken:
|
|
if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) {
|
|
error(left, Diagnostics.Left_side_of_comma_operator_is_unused_and_has_no_side_effects);
|
|
}
|
|
return rightType;
|
|
|
|
default:
|
|
return Debug.fail();
|
|
}
|
|
|
|
function bothAreBigIntLike(left: Type, right: Type): boolean {
|
|
return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike);
|
|
}
|
|
|
|
function checkAssignmentDeclaration(kind: AssignmentDeclarationKind, rightType: Type) {
|
|
if (kind === AssignmentDeclarationKind.ModuleExports) {
|
|
for (const prop of getPropertiesOfObjectType(rightType)) {
|
|
const propType = getTypeOfSymbol(prop);
|
|
if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) {
|
|
const name = prop.escapedName;
|
|
const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false);
|
|
if (symbol && symbol.declarations.some(isJSDocTypedefTag)) {
|
|
grammarErrorOnNode(symbol.declarations[0], Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name));
|
|
return grammarErrorOnNode(prop.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isEvalNode(node: Expression) {
|
|
return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval";
|
|
}
|
|
|
|
// Return true if there was no error, false if there was an error.
|
|
function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean {
|
|
const offendingSymbolOperand =
|
|
maybeTypeOfKind(leftType, TypeFlags.ESSymbolLike) ? left :
|
|
maybeTypeOfKind(rightType, TypeFlags.ESSymbolLike) ? right :
|
|
undefined;
|
|
|
|
if (offendingSymbolOperand) {
|
|
error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function getSuggestedBooleanOperator(operator: SyntaxKind): SyntaxKind | undefined {
|
|
switch (operator) {
|
|
case SyntaxKind.BarToken:
|
|
case SyntaxKind.BarEqualsToken:
|
|
return SyntaxKind.BarBarToken;
|
|
case SyntaxKind.CaretToken:
|
|
case SyntaxKind.CaretEqualsToken:
|
|
return SyntaxKind.ExclamationEqualsEqualsToken;
|
|
case SyntaxKind.AmpersandToken:
|
|
case SyntaxKind.AmpersandEqualsToken:
|
|
return SyntaxKind.AmpersandAmpersandToken;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function checkAssignmentOperator(valueType: Type): void {
|
|
if (produceDiagnostics && isAssignmentOperator(operator)) {
|
|
// TypeScript 1.0 spec (April 2014): 4.17
|
|
// An assignment of the form
|
|
// VarExpr = ValueExpr
|
|
// requires VarExpr to be classified as a reference
|
|
// A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1)
|
|
// and the type of the non-compound operation to be assignable to the type of VarExpr.
|
|
|
|
if (checkReferenceExpression(left,
|
|
Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access,
|
|
Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access)
|
|
&& (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) {
|
|
// to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
|
|
checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isAssignmentDeclaration(kind: AssignmentDeclarationKind) {
|
|
switch (kind) {
|
|
case AssignmentDeclarationKind.ModuleExports:
|
|
return true;
|
|
case AssignmentDeclarationKind.ExportsProperty:
|
|
case AssignmentDeclarationKind.Property:
|
|
case AssignmentDeclarationKind.Prototype:
|
|
case AssignmentDeclarationKind.PrototypeProperty:
|
|
case AssignmentDeclarationKind.ThisProperty:
|
|
const symbol = getSymbolOfNode(left);
|
|
const init = getAssignedExpandoInitializer(right);
|
|
return init && isObjectLiteralExpression(init) &&
|
|
symbol && hasEntries(symbol.exports);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if an error is reported
|
|
*/
|
|
function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean {
|
|
if (!typesAreCompatible(leftType, rightType)) {
|
|
reportOperatorError(typesAreCompatible);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) {
|
|
let wouldWorkWithAwait = false;
|
|
const errNode = errorNode || operatorToken;
|
|
if (isRelated) {
|
|
const awaitedLeftType = getAwaitedType(leftType);
|
|
const awaitedRightType = getAwaitedType(rightType);
|
|
wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType)
|
|
&& !!(awaitedLeftType && awaitedRightType)
|
|
&& isRelated(awaitedLeftType, awaitedRightType);
|
|
}
|
|
|
|
let effectiveLeft = leftType;
|
|
let effectiveRight = rightType;
|
|
if (!wouldWorkWithAwait && isRelated) {
|
|
[effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated);
|
|
}
|
|
const [leftStr, rightStr] = getTypeNamesForErrorDisplay(effectiveLeft, effectiveRight);
|
|
if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) {
|
|
errorAndMaybeSuggestAwait(
|
|
errNode,
|
|
wouldWorkWithAwait,
|
|
Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2,
|
|
tokenToString(operatorToken.kind),
|
|
leftStr,
|
|
rightStr,
|
|
);
|
|
}
|
|
}
|
|
|
|
function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) {
|
|
let typeName: string | undefined;
|
|
switch (operatorToken.kind) {
|
|
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
case SyntaxKind.EqualsEqualsToken:
|
|
typeName = "false";
|
|
break;
|
|
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsToken:
|
|
typeName = "true";
|
|
}
|
|
|
|
if (typeName) {
|
|
return errorAndMaybeSuggestAwait(
|
|
errNode,
|
|
maybeMissingAwait,
|
|
Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap,
|
|
typeName, leftStr, rightStr);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] {
|
|
let effectiveLeft = leftType;
|
|
let effectiveRight = rightType;
|
|
const leftBase = getBaseTypeOfLiteralType(leftType);
|
|
const rightBase = getBaseTypeOfLiteralType(rightType);
|
|
if (!isRelated(leftBase, rightBase)) {
|
|
effectiveLeft = leftBase;
|
|
effectiveRight = rightBase;
|
|
}
|
|
return [ effectiveLeft, effectiveRight ];
|
|
}
|
|
|
|
function checkYieldExpression(node: YieldExpression): Type {
|
|
// Grammar checking
|
|
if (produceDiagnostics) {
|
|
if (!(node.flags & NodeFlags.YieldContext)) {
|
|
grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body);
|
|
}
|
|
|
|
if (isInParameterInitializerBeforeContainingFunction(node)) {
|
|
error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer);
|
|
}
|
|
}
|
|
|
|
const func = getContainingFunction(node);
|
|
if (!func) return anyType;
|
|
const functionFlags = getFunctionFlags(func);
|
|
|
|
if (!(functionFlags & FunctionFlags.Generator)) {
|
|
// If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context.
|
|
return anyType;
|
|
}
|
|
|
|
const isAsync = (functionFlags & FunctionFlags.Async) !== 0;
|
|
if (node.asteriskToken) {
|
|
// Async generator functions prior to ESNext require the __await, __asyncDelegator,
|
|
// and __asyncValues helpers
|
|
if (isAsync && languageVersion < ScriptTarget.ESNext) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes);
|
|
}
|
|
|
|
// Generator functions prior to ES2015 require the __values helper
|
|
if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.Values);
|
|
}
|
|
}
|
|
|
|
// There is no point in doing an assignability check if the function
|
|
// has no explicit return type because the return type is directly computed
|
|
// from the yield expressions.
|
|
const returnType = getReturnTypeFromAnnotation(func);
|
|
const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync);
|
|
const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType;
|
|
const signatureNextType = iterationTypes && iterationTypes.nextType || anyType;
|
|
const resolvedSignatureNextType = isAsync ? getAwaitedType(signatureNextType) || anyType : signatureNextType;
|
|
const yieldExpressionType = node.expression ? checkExpression(node.expression) : undefinedWideningType;
|
|
const yieldedType = getYieldedTypeOfYieldExpression(node, yieldExpressionType, resolvedSignatureNextType, isAsync);
|
|
if (returnType && yieldedType) {
|
|
checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureYieldType, node.expression || node, node.expression);
|
|
}
|
|
|
|
if (node.asteriskToken) {
|
|
const use = isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar;
|
|
return getIterationTypeOfIterable(use, IterationTypeKind.Return, yieldExpressionType, node.expression)
|
|
|| anyType;
|
|
}
|
|
else if (returnType) {
|
|
return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync)
|
|
|| anyType;
|
|
}
|
|
|
|
return getContextualIterationType(IterationTypeKind.Next, func) || anyType;
|
|
}
|
|
|
|
function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type {
|
|
const type = checkTruthinessExpression(node.condition);
|
|
checkTestingKnownTruthyCallableType(node.condition, node.whenTrue, type);
|
|
const type1 = checkExpression(node.whenTrue, checkMode);
|
|
const type2 = checkExpression(node.whenFalse, checkMode);
|
|
return getUnionType([type1, type2], UnionReduction.Subtype);
|
|
}
|
|
|
|
function checkTemplateExpression(node: TemplateExpression): Type {
|
|
// We just want to check each expressions, but we are unconcerned with
|
|
// the type of each expression, as any value may be coerced into a string.
|
|
// It is worth asking whether this is what we really want though.
|
|
// A place where we actually *are* concerned with the expressions' types are
|
|
// in tagged templates.
|
|
forEach(node.templateSpans, templateSpan => {
|
|
if (maybeTypeOfKind(checkExpression(templateSpan.expression), TypeFlags.ESSymbolLike)) {
|
|
error(templateSpan.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String);
|
|
}
|
|
});
|
|
|
|
return stringType;
|
|
}
|
|
|
|
function getContextNode(node: Expression): Node {
|
|
if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) {
|
|
return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes)
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type {
|
|
const context = getContextNode(node);
|
|
const saveContextualType = context.contextualType;
|
|
const saveInferenceContext = context.inferenceContext;
|
|
try {
|
|
context.contextualType = contextualType;
|
|
context.inferenceContext = inferenceContext;
|
|
const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0));
|
|
// We strip literal freshness when an appropriate contextual type is present such that contextually typed
|
|
// literals always preserve their literal types (otherwise they might widen during type inference). An alternative
|
|
// here would be to not mark contextually typed literals as fresh in the first place.
|
|
const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ?
|
|
getRegularTypeOfLiteralType(type) : type;
|
|
return result;
|
|
}
|
|
finally {
|
|
// In the event our operation is canceled or some other exception occurs, reset the contextual type
|
|
// so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer
|
|
// may hold onto the checker that created it.
|
|
context.contextualType = saveContextualType;
|
|
context.inferenceContext = saveInferenceContext;
|
|
}
|
|
}
|
|
|
|
function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type {
|
|
const links = getNodeLinks(node);
|
|
if (!links.resolvedType) {
|
|
if (checkMode && checkMode !== CheckMode.Normal) {
|
|
return checkExpression(node, checkMode);
|
|
}
|
|
// When computing a type that we're going to cache, we need to ignore any ongoing control flow
|
|
// analysis because variables may have transient types in indeterminable states. Moving flowLoopStart
|
|
// to the top of the stack ensures all transient types are computed from a known point.
|
|
const saveFlowLoopStart = flowLoopStart;
|
|
const saveFlowTypeCache = flowTypeCache;
|
|
flowLoopStart = flowLoopCount;
|
|
flowTypeCache = undefined;
|
|
links.resolvedType = checkExpression(node, checkMode);
|
|
flowTypeCache = saveFlowTypeCache;
|
|
flowLoopStart = saveFlowLoopStart;
|
|
}
|
|
return links.resolvedType;
|
|
}
|
|
|
|
function isTypeAssertion(node: Expression) {
|
|
node = skipParentheses(node);
|
|
return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression;
|
|
}
|
|
|
|
function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) {
|
|
const initializer = getEffectiveInitializer(declaration)!;
|
|
const type = getQuickTypeOfExpression(initializer) ||
|
|
(contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer));
|
|
return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
|
|
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
|
|
padTupleType(type, declaration.name) : type;
|
|
}
|
|
|
|
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
|
|
const patternElements = pattern.elements;
|
|
const arity = getTypeReferenceArity(type);
|
|
const elementTypes = arity ? getTypeArguments(type).slice() : [];
|
|
for (let i = arity; i < patternElements.length; i++) {
|
|
const e = patternElements[i];
|
|
if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) {
|
|
elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType);
|
|
if (!isOmittedExpression(e) && !hasDefaultValue(e)) {
|
|
reportImplicitAny(e, anyType);
|
|
}
|
|
}
|
|
}
|
|
return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly);
|
|
}
|
|
|
|
function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) {
|
|
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type);
|
|
if (isInJSFile(declaration)) {
|
|
if (widened.flags & TypeFlags.Nullable) {
|
|
reportImplicitAny(declaration, anyType);
|
|
return anyType;
|
|
}
|
|
else if (isEmptyArrayLiteralType(widened)) {
|
|
reportImplicitAny(declaration, anyArrayType);
|
|
return anyArrayType;
|
|
}
|
|
}
|
|
return widened;
|
|
}
|
|
|
|
function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean {
|
|
if (contextualType) {
|
|
if (contextualType.flags & TypeFlags.UnionOrIntersection) {
|
|
const types = (<UnionType>contextualType).types;
|
|
return some(types, t => isLiteralOfContextualType(candidateType, t));
|
|
}
|
|
if (contextualType.flags & TypeFlags.InstantiableNonPrimitive) {
|
|
// If the contextual type is a type variable constrained to a primitive type, consider
|
|
// this a literal context for literals of that primitive type. For example, given a
|
|
// type parameter 'T extends string', infer string literal types for T.
|
|
const constraint = getBaseConstraintOfType(contextualType) || unknownType;
|
|
return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
|
|
maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
|
|
maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) ||
|
|
maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) ||
|
|
isLiteralOfContextualType(candidateType, constraint);
|
|
}
|
|
// If the contextual type is a literal of a particular primitive type, we consider this a
|
|
// literal context for all literals of that primitive type.
|
|
return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
|
|
contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
|
|
contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) ||
|
|
contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) ||
|
|
contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isConstContext(node: Expression): boolean {
|
|
const parent = node.parent;
|
|
return isAssertionExpression(parent) && isConstTypeReference(parent.type) ||
|
|
(isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) ||
|
|
(isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent)) && isConstContext(parent.parent);
|
|
}
|
|
|
|
function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type {
|
|
const type = checkExpression(node, checkMode, forceTuple);
|
|
return isConstContext(node) ? getRegularTypeOfLiteralType(type) :
|
|
isTypeAssertion(node) ? type :
|
|
getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node));
|
|
}
|
|
|
|
function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type {
|
|
// Do not use hasDynamicName here, because that returns false for well known symbols.
|
|
// We want to perform checkComputedPropertyName for all computed properties, including
|
|
// well known symbols.
|
|
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
|
|
checkComputedPropertyName(node.name);
|
|
}
|
|
|
|
return checkExpressionForMutableLocation(node.initializer, checkMode);
|
|
}
|
|
|
|
function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type {
|
|
// Grammar checking
|
|
checkGrammarMethod(node);
|
|
|
|
// Do not use hasDynamicName here, because that returns false for well known symbols.
|
|
// We want to perform checkComputedPropertyName for all computed properties, including
|
|
// well known symbols.
|
|
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
|
|
checkComputedPropertyName(node.name);
|
|
}
|
|
|
|
const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode);
|
|
return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode);
|
|
}
|
|
|
|
function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) {
|
|
if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) {
|
|
const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true);
|
|
const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true);
|
|
const signature = callSignature || constructSignature;
|
|
if (signature && signature.typeParameters) {
|
|
const contextualType = getApparentTypeOfContextualType(<Expression>node, ContextFlags.NoConstraints);
|
|
if (contextualType) {
|
|
const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false);
|
|
if (contextualSignature && !contextualSignature.typeParameters) {
|
|
if (checkMode & CheckMode.SkipGenericFunctions) {
|
|
skippedGenericFunction(node, checkMode);
|
|
return anyFunctionType;
|
|
}
|
|
const context = getInferenceContext(node)!;
|
|
// We have an expression that is an argument of a generic function for which we are performing
|
|
// type argument inference. The expression is of a function type with a single generic call
|
|
// signature and a contextual function type with a single non-generic call signature. Now check
|
|
// if the outer function returns a function type with a single non-generic call signature and
|
|
// if some of the outer function type parameters have no inferences so far. If so, we can
|
|
// potentially add inferred type parameters to the outer function return type.
|
|
const returnType = context.signature && getReturnTypeOfSignature(context.signature);
|
|
const returnSignature = returnType && getSingleCallOrConstructSignature(returnType);
|
|
if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) {
|
|
// Instantiate the signature with its own type parameters as type arguments, possibly
|
|
// renaming the type parameters to ensure they have unique names.
|
|
const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters);
|
|
const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters);
|
|
// Infer from the parameters of the instantiated signature to the parameters of the
|
|
// contextual signature starting with an empty set of inference candidates.
|
|
const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter));
|
|
applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => {
|
|
inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true);
|
|
});
|
|
if (some(inferences, hasInferenceCandidates)) {
|
|
// We have inference candidates, indicating that one or more type parameters are referenced
|
|
// in the parameter types of the contextual signature. Now also infer from the return type.
|
|
applyToReturnTypes(instantiatedSignature, contextualSignature, (source, target) => {
|
|
inferTypes(inferences, source, target);
|
|
});
|
|
// If the type parameters for which we produced candidates do not have any inferences yet,
|
|
// we adopt the new inference candidates and add the type parameters of the expression type
|
|
// to the set of inferred type parameters for the outer function return type.
|
|
if (!hasOverlappingInferences(context.inferences, inferences)) {
|
|
mergeInferences(context.inferences, inferences);
|
|
context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters);
|
|
return getOrCreateTypeFromSignature(instantiatedSignature);
|
|
}
|
|
}
|
|
}
|
|
return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function skippedGenericFunction(node: Node, checkMode: CheckMode) {
|
|
if (checkMode & CheckMode.Inferential) {
|
|
// We have skipped a generic function during inferential typing. Obtain the inference context and
|
|
// indicate this has occurred such that we know a second pass of inference is be needed.
|
|
const context = getInferenceContext(node)!;
|
|
context.flags |= InferenceFlags.SkippedGenericFunction;
|
|
}
|
|
}
|
|
|
|
function hasInferenceCandidates(info: InferenceInfo) {
|
|
return !!(info.candidates || info.contraCandidates);
|
|
}
|
|
|
|
function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) {
|
|
for (let i = 0; i < a.length; i++) {
|
|
if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) {
|
|
for (let i = 0; i < target.length; i++) {
|
|
if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) {
|
|
target[i] = source[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
function getUniqueTypeParameters(context: InferenceContext, typeParameters: readonly TypeParameter[]): readonly TypeParameter[] {
|
|
const result: TypeParameter[] = [];
|
|
let oldTypeParameters: TypeParameter[] | undefined;
|
|
let newTypeParameters: TypeParameter[] | undefined;
|
|
for (const tp of typeParameters) {
|
|
const name = tp.symbol.escapedName;
|
|
if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) {
|
|
const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name);
|
|
const symbol = createSymbol(SymbolFlags.TypeParameter, newName);
|
|
const newTypeParameter = createTypeParameter(symbol);
|
|
newTypeParameter.target = tp;
|
|
oldTypeParameters = append(oldTypeParameters, tp);
|
|
newTypeParameters = append(newTypeParameters, newTypeParameter);
|
|
result.push(newTypeParameter);
|
|
}
|
|
else {
|
|
result.push(tp);
|
|
}
|
|
}
|
|
if (newTypeParameters) {
|
|
const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters);
|
|
for (const tp of newTypeParameters) {
|
|
tp.mapper = mapper;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) {
|
|
return some(typeParameters, tp => tp.symbol.escapedName === name);
|
|
}
|
|
|
|
function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) {
|
|
let len = (<string>baseName).length;
|
|
while (len > 1 && (<string>baseName).charCodeAt(len - 1) >= CharacterCodes._0 && (<string>baseName).charCodeAt(len - 1) <= CharacterCodes._9) len--;
|
|
const s = (<string>baseName).slice(0, len);
|
|
for (let index = 1; true; index++) {
|
|
const augmentedName = <__String>(s + index);
|
|
if (!hasTypeParameterByName(typeParameters, augmentedName)) {
|
|
return augmentedName;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) {
|
|
const signature = getSingleCallSignature(funcType);
|
|
if (signature && !signature.typeParameters) {
|
|
return getReturnTypeOfSignature(signature);
|
|
}
|
|
}
|
|
|
|
function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) {
|
|
const funcType = checkExpression(expr.expression);
|
|
const nonOptionalType = getOptionalExpressionType(funcType, expr.expression);
|
|
const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType);
|
|
return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType);
|
|
}
|
|
|
|
/**
|
|
* Returns the type of an expression. Unlike checkExpression, this function is simply concerned
|
|
* with computing the type and may not fully check all contained sub-expressions for errors.
|
|
*/
|
|
function getTypeOfExpression(node: Expression) {
|
|
// Don't bother caching types that require no flow analysis and are quick to compute.
|
|
const quickType = getQuickTypeOfExpression(node);
|
|
if (quickType) {
|
|
return quickType;
|
|
}
|
|
// If a type has been cached for the node, return it.
|
|
if (node.flags & NodeFlags.TypeCached && flowTypeCache) {
|
|
const cachedType = flowTypeCache[getNodeId(node)];
|
|
if (cachedType) {
|
|
return cachedType;
|
|
}
|
|
}
|
|
const startInvocationCount = flowInvocationCount;
|
|
const type = checkExpression(node);
|
|
// If control flow analysis was required to determine the type, it is worth caching.
|
|
if (flowInvocationCount !== startInvocationCount) {
|
|
const cache = flowTypeCache || (flowTypeCache = []);
|
|
cache[getNodeId(node)] = type;
|
|
node.flags |= NodeFlags.TypeCached;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function getQuickTypeOfExpression(node: Expression) {
|
|
const expr = skipParentheses(node);
|
|
// Optimize for the common case of a call to a function with a single non-generic call
|
|
// signature where we can just fetch the return type without checking the arguments.
|
|
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) {
|
|
const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) :
|
|
getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression));
|
|
if (type) {
|
|
return type;
|
|
}
|
|
}
|
|
else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) {
|
|
return getTypeFromTypeNode((<TypeAssertion>expr).type);
|
|
}
|
|
else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral ||
|
|
node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) {
|
|
return checkExpression(node);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Returns the type of an expression. Unlike checkExpression, this function is simply concerned
|
|
* with computing the type and may not fully check all contained sub-expressions for errors.
|
|
* It is intended for uses where you know there is no contextual type,
|
|
* and requesting the contextual type might cause a circularity or other bad behaviour.
|
|
* It sets the contextual type of the node to any before calling getTypeOfExpression.
|
|
*/
|
|
function getContextFreeTypeOfExpression(node: Expression) {
|
|
const links = getNodeLinks(node);
|
|
if (links.contextFreeType) {
|
|
return links.contextFreeType;
|
|
}
|
|
const saveContextualType = node.contextualType;
|
|
node.contextualType = anyType;
|
|
try {
|
|
const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive);
|
|
return type;
|
|
}
|
|
finally {
|
|
// In the event our operation is canceled or some other exception occurs, reset the contextual type
|
|
// so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer
|
|
// may hold onto the checker that created it.
|
|
node.contextualType = saveContextualType;
|
|
}
|
|
}
|
|
|
|
function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type {
|
|
const saveCurrentNode = currentNode;
|
|
currentNode = node;
|
|
instantiationCount = 0;
|
|
const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple);
|
|
const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode);
|
|
if (isConstEnumObjectType(type)) {
|
|
checkConstEnumAccess(node, type);
|
|
}
|
|
currentNode = saveCurrentNode;
|
|
return type;
|
|
}
|
|
|
|
function checkConstEnumAccess(node: Expression | QualifiedName, type: Type) {
|
|
// enum object type for const enums are only permitted in:
|
|
// - 'left' in property access
|
|
// - 'object' in indexed access
|
|
// - target in rhs of import statement
|
|
const ok =
|
|
(node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).expression === node) ||
|
|
(node.parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>node.parent).expression === node) ||
|
|
((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(<Identifier>node) ||
|
|
(node.parent.kind === SyntaxKind.TypeQuery && (<TypeQueryNode>node.parent).exprName === node)) ||
|
|
(node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums
|
|
|
|
if (!ok) {
|
|
error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query);
|
|
}
|
|
|
|
if (compilerOptions.isolatedModules) {
|
|
Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum));
|
|
const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration;
|
|
if (constEnumDeclaration.flags & NodeFlags.Ambient) {
|
|
error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
|
|
const tag = isInJSFile(node) ? getJSDocTypeTag(node) : undefined;
|
|
if (tag) {
|
|
return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode);
|
|
}
|
|
return checkExpression(node.expression, checkMode);
|
|
}
|
|
|
|
function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type {
|
|
const kind = node.kind;
|
|
if (cancellationToken) {
|
|
// Only bother checking on a few construct kinds. We don't want to be excessively
|
|
// hitting the cancellation token on every node we check.
|
|
switch (kind) {
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
cancellationToken.throwIfCancellationRequested();
|
|
}
|
|
}
|
|
switch (kind) {
|
|
case SyntaxKind.Identifier:
|
|
return checkIdentifier(<Identifier>node);
|
|
case SyntaxKind.ThisKeyword:
|
|
return checkThisExpression(node);
|
|
case SyntaxKind.SuperKeyword:
|
|
return checkSuperExpression(node);
|
|
case SyntaxKind.NullKeyword:
|
|
return nullWideningType;
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
case SyntaxKind.StringLiteral:
|
|
return getFreshTypeOfLiteralType(getLiteralType((node as StringLiteralLike).text));
|
|
case SyntaxKind.NumericLiteral:
|
|
checkGrammarNumericLiteral(node as NumericLiteral);
|
|
return getFreshTypeOfLiteralType(getLiteralType(+(node as NumericLiteral).text));
|
|
case SyntaxKind.BigIntLiteral:
|
|
checkGrammarBigIntLiteral(node as BigIntLiteral);
|
|
return getFreshTypeOfLiteralType(getBigIntLiteralType(node as BigIntLiteral));
|
|
case SyntaxKind.TrueKeyword:
|
|
return trueType;
|
|
case SyntaxKind.FalseKeyword:
|
|
return falseType;
|
|
case SyntaxKind.TemplateExpression:
|
|
return checkTemplateExpression(<TemplateExpression>node);
|
|
case SyntaxKind.RegularExpressionLiteral:
|
|
return globalRegExpType;
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return checkArrayLiteral(<ArrayLiteralExpression>node, checkMode, forceTuple);
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
return checkObjectLiteral(<ObjectLiteralExpression>node, checkMode);
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
return checkPropertyAccessExpression(<PropertyAccessExpression>node);
|
|
case SyntaxKind.QualifiedName:
|
|
return checkQualifiedName(<QualifiedName>node);
|
|
case SyntaxKind.ElementAccessExpression:
|
|
return checkIndexedAccess(<ElementAccessExpression>node);
|
|
case SyntaxKind.CallExpression:
|
|
if ((<CallExpression>node).expression.kind === SyntaxKind.ImportKeyword) {
|
|
return checkImportCallExpression(<ImportCall>node);
|
|
}
|
|
// falls through
|
|
case SyntaxKind.NewExpression:
|
|
return checkCallExpression(<CallExpression>node, checkMode);
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node);
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return checkParenthesizedExpression(<ParenthesizedExpression>node, checkMode);
|
|
case SyntaxKind.ClassExpression:
|
|
return checkClassExpression(<ClassExpression>node);
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
return checkFunctionExpressionOrObjectLiteralMethod(<FunctionExpression | ArrowFunction>node, checkMode);
|
|
case SyntaxKind.TypeOfExpression:
|
|
return checkTypeOfExpression(<TypeOfExpression>node);
|
|
case SyntaxKind.TypeAssertionExpression:
|
|
case SyntaxKind.AsExpression:
|
|
return checkAssertion(<AssertionExpression>node);
|
|
case SyntaxKind.NonNullExpression:
|
|
return checkNonNullAssertion(<NonNullExpression>node);
|
|
case SyntaxKind.MetaProperty:
|
|
return checkMetaProperty(<MetaProperty>node);
|
|
case SyntaxKind.DeleteExpression:
|
|
return checkDeleteExpression(<DeleteExpression>node);
|
|
case SyntaxKind.VoidExpression:
|
|
return checkVoidExpression(<VoidExpression>node);
|
|
case SyntaxKind.AwaitExpression:
|
|
return checkAwaitExpression(<AwaitExpression>node);
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
return checkPrefixUnaryExpression(<PrefixUnaryExpression>node);
|
|
case SyntaxKind.PostfixUnaryExpression:
|
|
return checkPostfixUnaryExpression(<PostfixUnaryExpression>node);
|
|
case SyntaxKind.BinaryExpression:
|
|
return checkBinaryExpression(<BinaryExpression>node, checkMode);
|
|
case SyntaxKind.ConditionalExpression:
|
|
return checkConditionalExpression(<ConditionalExpression>node, checkMode);
|
|
case SyntaxKind.SpreadElement:
|
|
return checkSpreadExpression(<SpreadElement>node, checkMode);
|
|
case SyntaxKind.OmittedExpression:
|
|
return undefinedWideningType;
|
|
case SyntaxKind.YieldExpression:
|
|
return checkYieldExpression(<YieldExpression>node);
|
|
case SyntaxKind.SyntheticExpression:
|
|
return (<SyntheticExpression>node).type;
|
|
case SyntaxKind.JsxExpression:
|
|
return checkJsxExpression(<JsxExpression>node, checkMode);
|
|
case SyntaxKind.JsxElement:
|
|
return checkJsxElement(<JsxElement>node, checkMode);
|
|
case SyntaxKind.JsxSelfClosingElement:
|
|
return checkJsxSelfClosingElement(<JsxSelfClosingElement>node, checkMode);
|
|
case SyntaxKind.JsxFragment:
|
|
return checkJsxFragment(<JsxFragment>node);
|
|
case SyntaxKind.JsxAttributes:
|
|
return checkJsxAttributes(<JsxAttributes>node, checkMode);
|
|
case SyntaxKind.JsxOpeningElement:
|
|
Debug.fail("Shouldn't ever directly check a JsxOpeningElement");
|
|
}
|
|
return errorType;
|
|
}
|
|
|
|
// DECLARATION AND STATEMENT TYPE CHECKING
|
|
|
|
function checkTypeParameter(node: TypeParameterDeclaration) {
|
|
// Grammar Checking
|
|
if (node.expression) {
|
|
grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected);
|
|
}
|
|
|
|
checkSourceElement(node.constraint);
|
|
checkSourceElement(node.default);
|
|
const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node));
|
|
// Resolve base constraint to reveal circularity errors
|
|
getBaseConstraintOfType(typeParameter);
|
|
if (!hasNonCircularTypeParameterDefault(typeParameter)) {
|
|
error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter));
|
|
}
|
|
const constraintType = getConstraintOfTypeParameter(typeParameter);
|
|
const defaultType = getDefaultFromTypeParameter(typeParameter);
|
|
if (constraintType && defaultType) {
|
|
checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1);
|
|
}
|
|
if (produceDiagnostics) {
|
|
checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0);
|
|
}
|
|
}
|
|
|
|
function checkParameter(node: ParameterDeclaration) {
|
|
// Grammar checking
|
|
// It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the
|
|
// Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code
|
|
// or if its FunctionBody is strict code(11.1.5).
|
|
checkGrammarDecoratorsAndModifiers(node);
|
|
|
|
checkVariableLikeDeclaration(node);
|
|
const func = getContainingFunction(node)!;
|
|
if (hasModifier(node, ModifierFlags.ParameterPropertyModifier)) {
|
|
if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) {
|
|
error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation);
|
|
}
|
|
}
|
|
if (node.questionToken && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) {
|
|
error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature);
|
|
}
|
|
if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) {
|
|
if (func.parameters.indexOf(node) !== 0) {
|
|
error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, node.name.escapedText as string);
|
|
}
|
|
if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) {
|
|
error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter);
|
|
}
|
|
if (func.kind === SyntaxKind.ArrowFunction) {
|
|
error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter);
|
|
}
|
|
if (func.kind === SyntaxKind.GetAccessor || func.kind === SyntaxKind.SetAccessor) {
|
|
error(node, Diagnostics.get_and_set_accessors_cannot_declare_this_parameters);
|
|
}
|
|
}
|
|
|
|
// Only check rest parameter type if it's not a binding pattern. Since binding patterns are
|
|
// not allowed in a rest parameter, we already have an error from checkGrammarParameterList.
|
|
if (node.dotDotDotToken && !isBindingPattern(node.name) && !isTypeAssignableTo(getTypeOfSymbol(node.symbol), anyReadonlyArrayType)) {
|
|
error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type);
|
|
}
|
|
}
|
|
|
|
function checkTypePredicate(node: TypePredicateNode): void {
|
|
const parent = getTypePredicateParent(node);
|
|
if (!parent) {
|
|
// The parent must not be valid.
|
|
error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods);
|
|
return;
|
|
}
|
|
|
|
const signature = getSignatureFromDeclaration(parent);
|
|
const typePredicate = getTypePredicateOfSignature(signature);
|
|
if (!typePredicate) {
|
|
return;
|
|
}
|
|
|
|
checkSourceElement(node.type);
|
|
|
|
const { parameterName } = node;
|
|
if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) {
|
|
getTypeFromThisTypeNode(parameterName as ThisTypeNode);
|
|
}
|
|
else {
|
|
if (typePredicate.parameterIndex >= 0) {
|
|
if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) {
|
|
error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter);
|
|
}
|
|
else {
|
|
if (typePredicate.type) {
|
|
const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type);
|
|
checkTypeAssignableTo(typePredicate.type,
|
|
getTypeOfSymbol(signature.parameters[typePredicate.parameterIndex]),
|
|
node.type,
|
|
/*headMessage*/ undefined,
|
|
leadingError);
|
|
}
|
|
}
|
|
}
|
|
else if (parameterName) {
|
|
let hasReportedError = false;
|
|
for (const { name } of parent.parameters) {
|
|
if (isBindingPattern(name) &&
|
|
checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, parameterName, typePredicate.parameterName)) {
|
|
hasReportedError = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasReportedError) {
|
|
error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getTypePredicateParent(node: Node): SignatureDeclaration | undefined {
|
|
switch (node.parent.kind) {
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
const parent = <SignatureDeclaration>node.parent;
|
|
if (node === parent.type) {
|
|
return parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkIfTypePredicateVariableIsDeclaredInBindingPattern(
|
|
pattern: BindingPattern,
|
|
predicateVariableNode: Node,
|
|
predicateVariableName: string) {
|
|
for (const element of pattern.elements) {
|
|
if (isOmittedExpression(element)) {
|
|
continue;
|
|
}
|
|
|
|
const name = element.name;
|
|
if (name.kind === SyntaxKind.Identifier && name.escapedText === predicateVariableName) {
|
|
error(predicateVariableNode,
|
|
Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern,
|
|
predicateVariableName);
|
|
return true;
|
|
}
|
|
else if (name.kind === SyntaxKind.ArrayBindingPattern || name.kind === SyntaxKind.ObjectBindingPattern) {
|
|
if (checkIfTypePredicateVariableIsDeclaredInBindingPattern(
|
|
name,
|
|
predicateVariableNode,
|
|
predicateVariableName)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkSignatureDeclaration(node: SignatureDeclaration) {
|
|
// Grammar checking
|
|
if (node.kind === SyntaxKind.IndexSignature) {
|
|
checkGrammarIndexSignature(<SignatureDeclaration>node);
|
|
}
|
|
// TODO (yuisu): Remove this check in else-if when SyntaxKind.Construct is moved and ambient context is handled
|
|
else if (node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.ConstructorType ||
|
|
node.kind === SyntaxKind.CallSignature || node.kind === SyntaxKind.Constructor ||
|
|
node.kind === SyntaxKind.ConstructSignature) {
|
|
checkGrammarFunctionLikeDeclaration(<FunctionLikeDeclaration>node);
|
|
}
|
|
|
|
const functionFlags = getFunctionFlags(<FunctionLikeDeclaration>node);
|
|
if (!(functionFlags & FunctionFlags.Invalid)) {
|
|
// Async generators prior to ESNext require the __await and __asyncGenerator helpers
|
|
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < ScriptTarget.ESNext) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes);
|
|
}
|
|
|
|
// Async functions prior to ES2017 require the __awaiter helper
|
|
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter);
|
|
}
|
|
|
|
// Generator functions, Async functions, and Async Generator functions prior to
|
|
// ES2015 require the __generator helper
|
|
if ((functionFlags & FunctionFlags.AsyncGenerator) !== FunctionFlags.Normal && languageVersion < ScriptTarget.ES2015) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator);
|
|
}
|
|
}
|
|
|
|
checkTypeParameters(node.typeParameters);
|
|
|
|
forEach(node.parameters, checkParameter);
|
|
|
|
// TODO(rbuckton): Should we start checking JSDoc types?
|
|
if (node.type) {
|
|
checkSourceElement(node.type);
|
|
}
|
|
|
|
if (produceDiagnostics) {
|
|
checkCollisionWithArgumentsInGeneratedCode(node);
|
|
const returnTypeNode = getEffectiveReturnTypeNode(node);
|
|
if (noImplicitAny && !returnTypeNode) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ConstructSignature:
|
|
error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type);
|
|
break;
|
|
case SyntaxKind.CallSignature:
|
|
error(node, Diagnostics.Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (returnTypeNode) {
|
|
const functionFlags = getFunctionFlags(<FunctionDeclaration>node);
|
|
if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) {
|
|
const returnType = getTypeFromTypeNode(returnTypeNode);
|
|
if (returnType === voidType) {
|
|
error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation);
|
|
}
|
|
else {
|
|
// Naively, one could check that Generator<any, any, any> is assignable to the return type annotation.
|
|
// However, that would not catch the error in the following case.
|
|
//
|
|
// interface BadGenerator extends Iterable<number>, Iterator<string> { }
|
|
// function* g(): BadGenerator { } // Iterable and Iterator have different types!
|
|
//
|
|
const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType;
|
|
const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType;
|
|
const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType;
|
|
const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async));
|
|
checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode);
|
|
}
|
|
}
|
|
else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) {
|
|
checkAsyncFunctionReturnType(<FunctionLikeDeclaration>node, returnTypeNode);
|
|
}
|
|
}
|
|
if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) {
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) {
|
|
const instanceNames = createUnderscoreEscapedMap<DeclarationMeaning>();
|
|
const staticNames = createUnderscoreEscapedMap<DeclarationMeaning>();
|
|
// instance and static private identifiers share the same scope
|
|
const privateIdentifiers = createUnderscoreEscapedMap<DeclarationMeaning>();
|
|
for (const member of node.members) {
|
|
if (member.kind === SyntaxKind.Constructor) {
|
|
for (const param of (member as ConstructorDeclaration).parameters) {
|
|
if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(param.name)) {
|
|
addName(instanceNames, param.name, param.name.escapedText, DeclarationMeaning.GetOrSetAccessor);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
const isStatic = hasModifier(member, ModifierFlags.Static);
|
|
const name = member.name;
|
|
if (!name) {
|
|
return;
|
|
}
|
|
const names =
|
|
isPrivateIdentifier(name) ? privateIdentifiers :
|
|
isStatic ? staticNames :
|
|
instanceNames;
|
|
const memberName = name && getPropertyNameForPropertyNameNode(name);
|
|
if (memberName) {
|
|
switch (member.kind) {
|
|
case SyntaxKind.GetAccessor:
|
|
addName(names, name, memberName, DeclarationMeaning.GetAccessor);
|
|
break;
|
|
|
|
case SyntaxKind.SetAccessor:
|
|
addName(names, name, memberName, DeclarationMeaning.SetAccessor);
|
|
break;
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor);
|
|
break;
|
|
|
|
case SyntaxKind.MethodDeclaration:
|
|
addName(names, name, memberName, DeclarationMeaning.Method);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function addName(names: UnderscoreEscapedMap<DeclarationMeaning>, location: Node, name: __String, meaning: DeclarationMeaning) {
|
|
const prev = names.get(name);
|
|
if (prev) {
|
|
if (prev & DeclarationMeaning.Method) {
|
|
if (meaning !== DeclarationMeaning.Method) {
|
|
error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location));
|
|
}
|
|
}
|
|
else if (prev & meaning) {
|
|
error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location));
|
|
}
|
|
else {
|
|
names.set(name, prev | meaning);
|
|
}
|
|
}
|
|
else {
|
|
names.set(name, meaning);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Static members being set on a constructor function may conflict with built-in properties
|
|
* of Function. Esp. in ECMAScript 5 there are non-configurable and non-writable
|
|
* built-in properties. This check issues a transpile error when a class has a static
|
|
* member with the same name as a non-writable built-in property.
|
|
*
|
|
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.3
|
|
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5
|
|
* @see http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-function-constructor
|
|
* @see http://www.ecma-international.org/ecma-262/6.0/#sec-function-instances
|
|
*/
|
|
function checkClassForStaticPropertyNameConflicts(node: ClassLikeDeclaration) {
|
|
for (const member of node.members) {
|
|
const memberNameNode = member.name;
|
|
const isStatic = hasModifier(member, ModifierFlags.Static);
|
|
if (isStatic && memberNameNode) {
|
|
const memberName = getPropertyNameForPropertyNameNode(memberNameNode);
|
|
switch (memberName) {
|
|
case "name":
|
|
case "length":
|
|
case "caller":
|
|
case "arguments":
|
|
case "prototype":
|
|
const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1;
|
|
const className = getNameOfSymbolAsWritten(getSymbolOfNode(node));
|
|
error(memberNameNode, message, memberName, className);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) {
|
|
const names = createMap<boolean>();
|
|
for (const member of node.members) {
|
|
if (member.kind === SyntaxKind.PropertySignature) {
|
|
let memberName: string;
|
|
const name = member.name!;
|
|
switch (name.kind) {
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NumericLiteral:
|
|
memberName = name.text;
|
|
break;
|
|
case SyntaxKind.Identifier:
|
|
memberName = idText(name);
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
if (names.get(memberName)) {
|
|
error(getNameOfDeclaration(member.symbol.valueDeclaration), Diagnostics.Duplicate_identifier_0, memberName);
|
|
error(member.name, Diagnostics.Duplicate_identifier_0, memberName);
|
|
}
|
|
else {
|
|
names.set(memberName, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkTypeForDuplicateIndexSignatures(node: Node) {
|
|
if (node.kind === SyntaxKind.InterfaceDeclaration) {
|
|
const nodeSymbol = getSymbolOfNode(node as InterfaceDeclaration);
|
|
// in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration
|
|
// to prevent this run check only for the first declaration of a given kind
|
|
if (nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// TypeScript 1.0 spec (April 2014)
|
|
// 3.7.4: An object type can contain at most one string index signature and one numeric index signature.
|
|
// 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration
|
|
const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!);
|
|
if (indexSymbol) {
|
|
let seenNumericIndexer = false;
|
|
let seenStringIndexer = false;
|
|
for (const decl of indexSymbol.declarations) {
|
|
const declaration = <SignatureDeclaration>decl;
|
|
if (declaration.parameters.length === 1 && declaration.parameters[0].type) {
|
|
switch (declaration.parameters[0].type.kind) {
|
|
case SyntaxKind.StringKeyword:
|
|
if (!seenStringIndexer) {
|
|
seenStringIndexer = true;
|
|
}
|
|
else {
|
|
error(declaration, Diagnostics.Duplicate_string_index_signature);
|
|
}
|
|
break;
|
|
case SyntaxKind.NumberKeyword:
|
|
if (!seenNumericIndexer) {
|
|
seenNumericIndexer = true;
|
|
}
|
|
else {
|
|
error(declaration, Diagnostics.Duplicate_number_index_signature);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) {
|
|
// Grammar checking
|
|
if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name);
|
|
checkVariableLikeDeclaration(node);
|
|
|
|
// Private class fields transformation relies on WeakMaps.
|
|
if (isPrivateIdentifier(node.name) && languageVersion < ScriptTarget.ESNext) {
|
|
for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) {
|
|
getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers;
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkPropertySignature(node: PropertySignature) {
|
|
if (isPrivateIdentifier(node.name)) {
|
|
error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
|
|
}
|
|
return checkPropertyDeclaration(node);
|
|
}
|
|
|
|
function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) {
|
|
// Grammar checking
|
|
if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name);
|
|
|
|
if (isPrivateIdentifier(node.name)) {
|
|
error(node, Diagnostics.A_method_cannot_be_named_with_a_private_identifier);
|
|
}
|
|
|
|
// Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration
|
|
checkFunctionOrMethodDeclaration(node);
|
|
|
|
// Abstract methods cannot have an implementation.
|
|
// Extra checks are to avoid reporting multiple errors relating to the "abstractness" of the node.
|
|
if (hasModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) {
|
|
error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name));
|
|
}
|
|
}
|
|
|
|
function checkConstructorDeclaration(node: ConstructorDeclaration) {
|
|
// Grammar check on signature of constructor and modifier of the constructor is done in checkSignatureDeclaration function.
|
|
checkSignatureDeclaration(node);
|
|
// Grammar check for checking only related to constructorDeclaration
|
|
if (!checkGrammarConstructorTypeParameters(node)) checkGrammarConstructorTypeAnnotation(node);
|
|
|
|
checkSourceElement(node.body);
|
|
|
|
const symbol = getSymbolOfNode(node);
|
|
const firstDeclaration = getDeclarationOfKind(symbol, node.kind);
|
|
|
|
// Only type check the symbol once
|
|
if (node === firstDeclaration) {
|
|
checkFunctionOrConstructorSymbol(symbol);
|
|
}
|
|
|
|
// exit early in the case of signature - super checks are not relevant to them
|
|
if (nodeIsMissing(node.body)) {
|
|
return;
|
|
}
|
|
|
|
if (!produceDiagnostics) {
|
|
return;
|
|
}
|
|
|
|
function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean {
|
|
if (isPrivateIdentifierPropertyDeclaration(n)) {
|
|
return true;
|
|
}
|
|
return n.kind === SyntaxKind.PropertyDeclaration &&
|
|
!hasModifier(n, ModifierFlags.Static) &&
|
|
!!(<PropertyDeclaration>n).initializer;
|
|
}
|
|
|
|
// TS 1.0 spec (April 2014): 8.3.2
|
|
// Constructors of classes with no extends clause may not contain super calls, whereas
|
|
// constructors of derived classes must contain at least one super call somewhere in their function body.
|
|
const containingClassDecl = <ClassDeclaration>node.parent;
|
|
if (getClassExtendsHeritageElement(containingClassDecl)) {
|
|
captureLexicalThis(node.parent, containingClassDecl);
|
|
const classExtendsNull = classDeclarationExtendsNull(containingClassDecl);
|
|
const superCall = getSuperCallInConstructor(node);
|
|
if (superCall) {
|
|
if (classExtendsNull) {
|
|
error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null);
|
|
}
|
|
|
|
// The first statement in the body of a constructor (excluding prologue directives) must be a super call
|
|
// if both of the following are true:
|
|
// - The containing class is a derived class.
|
|
// - The constructor declares parameter properties
|
|
// or the containing class declares instance member variables with initializers.
|
|
const superCallShouldBeFirst =
|
|
some((<ClassDeclaration>node.parent).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) ||
|
|
some(node.parameters, p => hasModifier(p, ModifierFlags.ParameterPropertyModifier));
|
|
|
|
// Skip past any prologue directives to find the first statement
|
|
// to ensure that it was a super call.
|
|
if (superCallShouldBeFirst) {
|
|
const statements = node.body!.statements;
|
|
let superCallStatement: ExpressionStatement | undefined;
|
|
|
|
for (const statement of statements) {
|
|
if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((<ExpressionStatement>statement).expression)) {
|
|
superCallStatement = <ExpressionStatement>statement;
|
|
break;
|
|
}
|
|
if (!isPrologueDirective(statement)) {
|
|
break;
|
|
}
|
|
}
|
|
if (!superCallStatement) {
|
|
error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_parameter_properties_or_private_identifiers);
|
|
}
|
|
}
|
|
}
|
|
else if (!classExtendsNull) {
|
|
error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkAccessorDeclaration(node: AccessorDeclaration) {
|
|
if (produceDiagnostics) {
|
|
// Grammar checking accessors
|
|
if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) checkGrammarComputedPropertyName(node.name);
|
|
|
|
checkDecorators(node);
|
|
checkSignatureDeclaration(node);
|
|
if (node.kind === SyntaxKind.GetAccessor) {
|
|
if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) {
|
|
if (!(node.flags & NodeFlags.HasExplicitReturn)) {
|
|
error(node.name, Diagnostics.A_get_accessor_must_return_a_value);
|
|
}
|
|
}
|
|
}
|
|
// Do not use hasDynamicName here, because that returns false for well known symbols.
|
|
// We want to perform checkComputedPropertyName for all computed properties, including
|
|
// well known symbols.
|
|
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
|
|
checkComputedPropertyName(node.name);
|
|
}
|
|
if (isPrivateIdentifier(node.name)) {
|
|
error(node.name, Diagnostics.An_accessor_cannot_be_named_with_a_private_identifier);
|
|
}
|
|
if (!hasNonBindableDynamicName(node)) {
|
|
// TypeScript 1.0 spec (April 2014): 8.4.3
|
|
// Accessors for the same member name must specify the same accessibility.
|
|
const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor;
|
|
const otherAccessor = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(node), otherKind);
|
|
if (otherAccessor) {
|
|
const nodeFlags = getModifierFlags(node);
|
|
const otherFlags = getModifierFlags(otherAccessor);
|
|
if ((nodeFlags & ModifierFlags.AccessibilityModifier) !== (otherFlags & ModifierFlags.AccessibilityModifier)) {
|
|
error(node.name, Diagnostics.Getter_and_setter_accessors_do_not_agree_in_visibility);
|
|
}
|
|
if ((nodeFlags & ModifierFlags.Abstract) !== (otherFlags & ModifierFlags.Abstract)) {
|
|
error(node.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract);
|
|
}
|
|
|
|
// TypeScript 1.0 spec (April 2014): 4.5
|
|
// If both accessors include type annotations, the specified types must be identical.
|
|
checkAccessorDeclarationTypesIdentical(node, otherAccessor, getAnnotatedAccessorType, Diagnostics.get_and_set_accessor_must_have_the_same_type);
|
|
checkAccessorDeclarationTypesIdentical(node, otherAccessor, getThisTypeOfDeclaration, Diagnostics.get_and_set_accessor_must_have_the_same_this_type);
|
|
}
|
|
}
|
|
const returnType = getTypeOfAccessors(getSymbolOfNode(node));
|
|
if (node.kind === SyntaxKind.GetAccessor) {
|
|
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType);
|
|
}
|
|
}
|
|
checkSourceElement(node.body);
|
|
}
|
|
|
|
function checkAccessorDeclarationTypesIdentical(first: AccessorDeclaration, second: AccessorDeclaration, getAnnotatedType: (a: AccessorDeclaration) => Type | undefined, message: DiagnosticMessage) {
|
|
const firstType = getAnnotatedType(first);
|
|
const secondType = getAnnotatedType(second);
|
|
if (firstType && secondType && !isTypeIdenticalTo(firstType, secondType)) {
|
|
error(first, message);
|
|
}
|
|
}
|
|
|
|
function checkMissingDeclaration(node: Node) {
|
|
checkDecorators(node);
|
|
}
|
|
|
|
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] {
|
|
return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters,
|
|
getMinTypeArgumentCount(typeParameters), isInJSFile(node));
|
|
}
|
|
|
|
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean {
|
|
let typeArguments: Type[] | undefined;
|
|
let mapper: TypeMapper | undefined;
|
|
let result = true;
|
|
for (let i = 0; i < typeParameters.length; i++) {
|
|
const constraint = getConstraintOfTypeParameter(typeParameters[i]);
|
|
if (constraint) {
|
|
if (!typeArguments) {
|
|
typeArguments = getEffectiveTypeArguments(node, typeParameters);
|
|
mapper = createTypeMapper(typeParameters, typeArguments);
|
|
}
|
|
result = result && checkTypeAssignableTo(
|
|
typeArguments[i],
|
|
instantiateType(constraint, mapper),
|
|
node.typeArguments![i],
|
|
Diagnostics.Type_0_does_not_satisfy_the_constraint_1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) {
|
|
const type = getTypeFromTypeReference(node);
|
|
if (type !== errorType) {
|
|
const symbol = getNodeLinks(node).resolvedSymbol;
|
|
if (symbol) {
|
|
return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters ||
|
|
(getObjectFlags(type) & ObjectFlags.Reference ? (<TypeReference>type).target.localTypeParameters : undefined);
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) {
|
|
checkGrammarTypeArguments(node, node.typeArguments);
|
|
if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) {
|
|
grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments);
|
|
}
|
|
forEach(node.typeArguments, checkSourceElement);
|
|
const type = getTypeFromTypeReference(node);
|
|
if (type !== errorType) {
|
|
if (node.typeArguments && produceDiagnostics) {
|
|
const typeParameters = getTypeParametersForTypeReference(node);
|
|
if (typeParameters) {
|
|
checkTypeArgumentConstraints(node, typeParameters);
|
|
}
|
|
}
|
|
if (type.flags & TypeFlags.Enum && getNodeLinks(node).resolvedSymbol!.flags & SymbolFlags.EnumMember) {
|
|
error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type));
|
|
}
|
|
}
|
|
}
|
|
|
|
function getTypeArgumentConstraint(node: TypeNode): Type | undefined {
|
|
const typeReferenceNode = tryCast(node.parent, isTypeReferenceType);
|
|
if (!typeReferenceNode) return undefined;
|
|
const typeParameters = getTypeParametersForTypeReference(typeReferenceNode)!; // TODO: GH#18217
|
|
const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]);
|
|
return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters)));
|
|
}
|
|
|
|
function checkTypeQuery(node: TypeQueryNode) {
|
|
getTypeFromTypeQueryNode(node);
|
|
}
|
|
|
|
function checkTypeLiteral(node: TypeLiteralNode) {
|
|
forEach(node.members, checkSourceElement);
|
|
if (produceDiagnostics) {
|
|
const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node);
|
|
checkIndexConstraints(type);
|
|
checkTypeForDuplicateIndexSignatures(node);
|
|
checkObjectTypeForDuplicateDeclarations(node);
|
|
}
|
|
}
|
|
|
|
function checkArrayType(node: ArrayTypeNode) {
|
|
checkSourceElement(node.elementType);
|
|
}
|
|
|
|
function checkTupleType(node: TupleTypeNode) {
|
|
const elementTypes = node.elementTypes;
|
|
let seenOptionalElement = false;
|
|
for (let i = 0; i < elementTypes.length; i++) {
|
|
const e = elementTypes[i];
|
|
if (e.kind === SyntaxKind.RestType) {
|
|
if (i !== elementTypes.length - 1) {
|
|
grammarErrorOnNode(e, Diagnostics.A_rest_element_must_be_last_in_a_tuple_type);
|
|
break;
|
|
}
|
|
if (!isArrayType(getTypeFromTypeNode((<RestTypeNode>e).type))) {
|
|
error(e, Diagnostics.A_rest_element_type_must_be_an_array_type);
|
|
}
|
|
}
|
|
else if (e.kind === SyntaxKind.OptionalType) {
|
|
seenOptionalElement = true;
|
|
}
|
|
else if (seenOptionalElement) {
|
|
grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element);
|
|
break;
|
|
}
|
|
}
|
|
forEach(node.elementTypes, checkSourceElement);
|
|
}
|
|
|
|
function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) {
|
|
forEach(node.types, checkSourceElement);
|
|
}
|
|
|
|
function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) {
|
|
if (!(type.flags & TypeFlags.IndexedAccess)) {
|
|
return type;
|
|
}
|
|
// Check if the index type is assignable to 'keyof T' for the object type.
|
|
const objectType = (<IndexedAccessType>type).objectType;
|
|
const indexType = (<IndexedAccessType>type).indexType;
|
|
if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) {
|
|
if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) &&
|
|
getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(<MappedType>objectType) & MappedTypeModifiers.IncludeReadonly) {
|
|
error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
|
|
}
|
|
return type;
|
|
}
|
|
// Check if we're indexing with a numeric type and if either object or index types
|
|
// is a generic type with a constraint that has a numeric index signature.
|
|
const apparentObjectType = getApparentType(objectType);
|
|
if (getIndexInfoOfType(apparentObjectType, IndexKind.Number) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
|
|
return type;
|
|
}
|
|
if (isGenericObjectType(objectType)) {
|
|
const propertyName = getPropertyNameFromIndex(indexType, accessNode);
|
|
if (propertyName) {
|
|
const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName));
|
|
if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) {
|
|
error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName));
|
|
return errorType;
|
|
}
|
|
}
|
|
}
|
|
error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
|
|
return errorType;
|
|
}
|
|
|
|
function checkIndexedAccessType(node: IndexedAccessTypeNode) {
|
|
checkSourceElement(node.objectType);
|
|
checkSourceElement(node.indexType);
|
|
checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node);
|
|
}
|
|
|
|
function checkMappedType(node: MappedTypeNode) {
|
|
checkSourceElement(node.typeParameter);
|
|
checkSourceElement(node.type);
|
|
|
|
if (!node.type) {
|
|
reportImplicitAny(node, anyType);
|
|
}
|
|
|
|
const type = <MappedType>getTypeFromMappedTypeNode(node);
|
|
const constraintType = getConstraintTypeFromMappedType(type);
|
|
checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter));
|
|
}
|
|
|
|
function checkThisType(node: ThisTypeNode) {
|
|
getTypeFromThisTypeNode(node);
|
|
}
|
|
|
|
function checkTypeOperator(node: TypeOperatorNode) {
|
|
checkGrammarTypeOperatorNode(node);
|
|
checkSourceElement(node.type);
|
|
}
|
|
|
|
function checkConditionalType(node: ConditionalTypeNode) {
|
|
forEachChild(node, checkSourceElement);
|
|
}
|
|
|
|
function checkInferType(node: InferTypeNode) {
|
|
if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (<ConditionalTypeNode>n.parent).extendsType === n)) {
|
|
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
|
|
}
|
|
checkSourceElement(node.typeParameter);
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
|
|
function checkImportType(node: ImportTypeNode) {
|
|
checkSourceElement(node.argument);
|
|
getTypeFromTypeNode(node);
|
|
}
|
|
|
|
function isPrivateWithinAmbient(node: Node): boolean {
|
|
return (hasModifier(node, ModifierFlags.Private) || isPrivateIdentifierPropertyDeclaration(node)) && !!(node.flags & NodeFlags.Ambient);
|
|
}
|
|
|
|
function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags {
|
|
let flags = getCombinedModifierFlags(n);
|
|
|
|
// children of classes (even ambient classes) should not be marked as ambient or export
|
|
// because those flags have no useful semantics there.
|
|
if (n.parent.kind !== SyntaxKind.InterfaceDeclaration &&
|
|
n.parent.kind !== SyntaxKind.ClassDeclaration &&
|
|
n.parent.kind !== SyntaxKind.ClassExpression &&
|
|
n.flags & NodeFlags.Ambient) {
|
|
if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) {
|
|
// It is nested in an ambient context, which means it is automatically exported
|
|
flags |= ModifierFlags.Export;
|
|
}
|
|
flags |= ModifierFlags.Ambient;
|
|
}
|
|
|
|
return flags & flagsToCheck;
|
|
}
|
|
|
|
function checkFunctionOrConstructorSymbol(symbol: Symbol): void {
|
|
if (!produceDiagnostics) {
|
|
return;
|
|
}
|
|
|
|
function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration {
|
|
// Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration
|
|
// Error on all deviations from this canonical set of flags
|
|
// The caveat is that if some overloads are defined in lib.d.ts, we don't want to
|
|
// report the errors on those. To achieve this, we will say that the implementation is
|
|
// the canonical signature only if it is in the same container as the first overload
|
|
const implementationSharesContainerWithFirstOverload = implementation !== undefined && implementation.parent === overloads[0].parent;
|
|
return implementationSharesContainerWithFirstOverload ? implementation! : overloads[0];
|
|
}
|
|
|
|
function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void {
|
|
// Error if some overloads have a flag that is not shared by all overloads. To find the
|
|
// deviations, we XOR someOverloadFlags with allOverloadFlags
|
|
const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags;
|
|
if (someButNotAllOverloadFlags !== 0) {
|
|
const canonicalFlags = getEffectiveDeclarationFlags(getCanonicalOverload(overloads, implementation), flagsToCheck);
|
|
|
|
forEach(overloads, o => {
|
|
const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags;
|
|
if (deviation & ModifierFlags.Export) {
|
|
error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported);
|
|
}
|
|
else if (deviation & ModifierFlags.Ambient) {
|
|
error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient);
|
|
}
|
|
else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) {
|
|
error(getNameOfDeclaration(o) || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected);
|
|
}
|
|
else if (deviation & ModifierFlags.Abstract) {
|
|
error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function checkQuestionTokenAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, someHaveQuestionToken: boolean, allHaveQuestionToken: boolean): void {
|
|
if (someHaveQuestionToken !== allHaveQuestionToken) {
|
|
const canonicalHasQuestionToken = hasQuestionToken(getCanonicalOverload(overloads, implementation));
|
|
forEach(overloads, o => {
|
|
const deviation = hasQuestionToken(o) !== canonicalHasQuestionToken;
|
|
if (deviation) {
|
|
error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_optional_or_required);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract;
|
|
let someNodeFlags: ModifierFlags = ModifierFlags.None;
|
|
let allNodeFlags = flagsToCheck;
|
|
let someHaveQuestionToken = false;
|
|
let allHaveQuestionToken = true;
|
|
let hasOverloads = false;
|
|
let bodyDeclaration: FunctionLikeDeclaration | undefined;
|
|
let lastSeenNonAmbientDeclaration: FunctionLikeDeclaration | undefined;
|
|
let previousDeclaration: SignatureDeclaration | undefined;
|
|
|
|
const declarations = symbol.declarations;
|
|
const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0;
|
|
|
|
function reportImplementationExpectedError(node: SignatureDeclaration): void {
|
|
if (node.name && nodeIsMissing(node.name)) {
|
|
return;
|
|
}
|
|
|
|
let seen = false;
|
|
const subsequentNode = forEachChild(node.parent, c => {
|
|
if (seen) {
|
|
return c;
|
|
}
|
|
else {
|
|
seen = c === node;
|
|
}
|
|
});
|
|
// We may be here because of some extra nodes between overloads that could not be parsed into a valid node.
|
|
// In this case the subsequent node is not really consecutive (.pos !== node.end), and we must ignore it here.
|
|
if (subsequentNode && subsequentNode.pos === node.end) {
|
|
if (subsequentNode.kind === node.kind) {
|
|
const errorNode: Node = (<FunctionLikeDeclaration>subsequentNode).name || subsequentNode;
|
|
const subsequentName = (<FunctionLikeDeclaration>subsequentNode).name;
|
|
if (node.name && subsequentName && (
|
|
// both are private identifiers
|
|
isPrivateIdentifier(node.name) && isPrivateIdentifier(subsequentName) && node.name.escapedText === subsequentName.escapedText ||
|
|
// Both are computed property names
|
|
// TODO: GH#17345: These are methods, so handle computed name case. (`Always allowing computed property names is *not* the correct behavior!)
|
|
isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) ||
|
|
// Both are literal property names that are the same.
|
|
isPropertyNameLiteral(node.name) && isPropertyNameLiteral(subsequentName) &&
|
|
getEscapedTextOfIdentifierOrLiteral(node.name) === getEscapedTextOfIdentifierOrLiteral(subsequentName)
|
|
)) {
|
|
const reportError =
|
|
(node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) &&
|
|
hasModifier(node, ModifierFlags.Static) !== hasModifier(subsequentNode, ModifierFlags.Static);
|
|
// we can get here in two cases
|
|
// 1. mixed static and instance class members
|
|
// 2. something with the same name was defined before the set of overloads that prevents them from merging
|
|
// here we'll report error only for the first case since for second we should already report error in binder
|
|
if (reportError) {
|
|
const diagnostic = hasModifier(node, ModifierFlags.Static) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static;
|
|
error(errorNode, diagnostic);
|
|
}
|
|
return;
|
|
}
|
|
if (nodeIsPresent((<FunctionLikeDeclaration>subsequentNode).body)) {
|
|
error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
const errorNode: Node = node.name || node;
|
|
if (isConstructor) {
|
|
error(errorNode, Diagnostics.Constructor_implementation_is_missing);
|
|
}
|
|
else {
|
|
// Report different errors regarding non-consecutive blocks of declarations depending on whether
|
|
// the node in question is abstract.
|
|
if (hasModifier(node, ModifierFlags.Abstract)) {
|
|
error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive);
|
|
}
|
|
else {
|
|
error(errorNode, Diagnostics.Function_implementation_is_missing_or_not_immediately_following_the_declaration);
|
|
}
|
|
}
|
|
}
|
|
|
|
let duplicateFunctionDeclaration = false;
|
|
let multipleConstructorImplementation = false;
|
|
let hasNonAmbientClass = false;
|
|
for (const current of declarations) {
|
|
const node = <SignatureDeclaration | ClassDeclaration | ClassExpression>current;
|
|
const inAmbientContext = node.flags & NodeFlags.Ambient;
|
|
const inAmbientContextOrInterface = node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral || inAmbientContext;
|
|
if (inAmbientContextOrInterface) {
|
|
// check if declarations are consecutive only if they are non-ambient
|
|
// 1. ambient declarations can be interleaved
|
|
// i.e. this is legal
|
|
// declare function foo();
|
|
// declare function bar();
|
|
// declare function foo();
|
|
// 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one
|
|
previousDeclaration = undefined;
|
|
}
|
|
|
|
if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) {
|
|
hasNonAmbientClass = true;
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) {
|
|
const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck);
|
|
someNodeFlags |= currentNodeFlags;
|
|
allNodeFlags &= currentNodeFlags;
|
|
someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node);
|
|
allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node);
|
|
|
|
if (nodeIsPresent((node as FunctionLikeDeclaration).body) && bodyDeclaration) {
|
|
if (isConstructor) {
|
|
multipleConstructorImplementation = true;
|
|
}
|
|
else {
|
|
duplicateFunctionDeclaration = true;
|
|
}
|
|
}
|
|
else if (previousDeclaration && previousDeclaration.parent === node.parent && previousDeclaration.end !== node.pos) {
|
|
reportImplementationExpectedError(previousDeclaration);
|
|
}
|
|
|
|
if (nodeIsPresent((node as FunctionLikeDeclaration).body)) {
|
|
if (!bodyDeclaration) {
|
|
bodyDeclaration = node as FunctionLikeDeclaration;
|
|
}
|
|
}
|
|
else {
|
|
hasOverloads = true;
|
|
}
|
|
|
|
previousDeclaration = node;
|
|
|
|
if (!inAmbientContextOrInterface) {
|
|
lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (multipleConstructorImplementation) {
|
|
forEach(declarations, declaration => {
|
|
error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed);
|
|
});
|
|
}
|
|
|
|
if (duplicateFunctionDeclaration) {
|
|
forEach(declarations, declaration => {
|
|
error(getNameOfDeclaration(declaration), Diagnostics.Duplicate_function_implementation);
|
|
});
|
|
}
|
|
|
|
if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function) {
|
|
// A non-ambient class cannot be an implementation for a non-constructor function/class merge
|
|
// TODO: The below just replicates our older error from when classes and functions were
|
|
// entirely unable to merge - a more helpful message like "Class declaration cannot implement overload list"
|
|
// might be warranted. :shrug:
|
|
forEach(declarations, declaration => {
|
|
addDuplicateDeclarationError(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_identifier_0, symbolName(symbol), filter(declarations, d => d !== declaration));
|
|
});
|
|
}
|
|
|
|
// Abstract methods can't have an implementation -- in particular, they don't need one.
|
|
if (lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body &&
|
|
!hasModifier(lastSeenNonAmbientDeclaration, ModifierFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) {
|
|
reportImplementationExpectedError(lastSeenNonAmbientDeclaration);
|
|
}
|
|
|
|
if (hasOverloads) {
|
|
checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags);
|
|
checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken);
|
|
|
|
if (bodyDeclaration) {
|
|
const signatures = getSignaturesOfSymbol(symbol);
|
|
const bodySignature = getSignatureFromDeclaration(bodyDeclaration);
|
|
for (const signature of signatures) {
|
|
if (!isImplementationCompatibleWithOverload(bodySignature, signature)) {
|
|
addRelatedInfo(
|
|
error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature),
|
|
createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here)
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkExportsOnMergedDeclarations(node: Declaration): void {
|
|
if (!produceDiagnostics) {
|
|
return;
|
|
}
|
|
|
|
// if localSymbol is defined on node then node itself is exported - check is required
|
|
let symbol = node.localSymbol;
|
|
if (!symbol) {
|
|
// local symbol is undefined => this declaration is non-exported.
|
|
// however symbol might contain other declarations that are exported
|
|
symbol = getSymbolOfNode(node)!;
|
|
if (!symbol.exportSymbol) {
|
|
// this is a pure local symbol (all declarations are non-exported) - no need to check anything
|
|
return;
|
|
}
|
|
}
|
|
|
|
// run the check only for the first declaration in the list
|
|
if (getDeclarationOfKind(symbol, node.kind) !== node) {
|
|
return;
|
|
}
|
|
|
|
let exportedDeclarationSpaces = DeclarationSpaces.None;
|
|
let nonExportedDeclarationSpaces = DeclarationSpaces.None;
|
|
let defaultExportedDeclarationSpaces = DeclarationSpaces.None;
|
|
for (const d of symbol.declarations) {
|
|
const declarationSpaces = getDeclarationSpaces(d);
|
|
const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default);
|
|
|
|
if (effectiveDeclarationFlags & ModifierFlags.Export) {
|
|
if (effectiveDeclarationFlags & ModifierFlags.Default) {
|
|
defaultExportedDeclarationSpaces |= declarationSpaces;
|
|
}
|
|
else {
|
|
exportedDeclarationSpaces |= declarationSpaces;
|
|
}
|
|
}
|
|
else {
|
|
nonExportedDeclarationSpaces |= declarationSpaces;
|
|
}
|
|
}
|
|
|
|
// Spaces for anything not declared a 'default export'.
|
|
const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces;
|
|
|
|
const commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces;
|
|
const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces;
|
|
|
|
if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) {
|
|
// declaration spaces for exported and non-exported declarations intersect
|
|
for (const d of symbol.declarations) {
|
|
const declarationSpaces = getDeclarationSpaces(d);
|
|
|
|
const name = getNameOfDeclaration(d);
|
|
// Only error on the declarations that contributed to the intersecting spaces.
|
|
if (declarationSpaces & commonDeclarationSpacesForDefaultAndNonDefault) {
|
|
error(name, Diagnostics.Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead, declarationNameToString(name));
|
|
}
|
|
else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) {
|
|
error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name));
|
|
}
|
|
}
|
|
}
|
|
|
|
function getDeclarationSpaces(decl: Declaration): DeclarationSpaces {
|
|
let d = decl as Node;
|
|
switch (d.kind) {
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
|
|
// A jsdoc typedef and callback are, by definition, type aliases.
|
|
// falls through
|
|
case SyntaxKind.JSDocTypedefTag:
|
|
case SyntaxKind.JSDocCallbackTag:
|
|
case SyntaxKind.JSDocEnumTag:
|
|
return DeclarationSpaces.ExportType;
|
|
case SyntaxKind.ModuleDeclaration:
|
|
return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated
|
|
? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue
|
|
: DeclarationSpaces.ExportNamespace;
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.EnumMember:
|
|
return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue;
|
|
case SyntaxKind.SourceFile:
|
|
return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace;
|
|
case SyntaxKind.ExportAssignment:
|
|
// Export assigned entity name expressions act as aliases and should fall through, otherwise they export values
|
|
if (!isEntityNameExpression((d as ExportAssignment).expression)) {
|
|
return DeclarationSpaces.ExportValue;
|
|
}
|
|
d = (d as ExportAssignment).expression;
|
|
|
|
// The below options all declare an Alias, which is allowed to merge with other values within the importing module.
|
|
// falls through
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
case SyntaxKind.NamespaceImport:
|
|
case SyntaxKind.ImportClause:
|
|
let result = DeclarationSpaces.None;
|
|
const target = resolveAlias(getSymbolOfNode(d)!);
|
|
forEach(target.declarations, d => { result |= getDeclarationSpaces(d); });
|
|
return result;
|
|
case SyntaxKind.VariableDeclaration:
|
|
case SyntaxKind.BindingElement:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591
|
|
case SyntaxKind.Identifier: // https://github.com/microsoft/TypeScript/issues/36098
|
|
// Identifiers are used as declarations of assignment declarations whose parents may be
|
|
// SyntaxKind.CallExpression - `Object.defineProperty(thing, "aField", {value: 42});`
|
|
// SyntaxKind.ElementAccessExpression - `thing["aField"] = 42;` or `thing["aField"];` (with a doc comment on it)
|
|
// or SyntaxKind.PropertyAccessExpression - `thing.aField = 42;`
|
|
// all of which are pretty much always values, or at least imply a value meaning.
|
|
// It may be apprpriate to treat these as aliases in the future.
|
|
return DeclarationSpaces.ExportValue;
|
|
default:
|
|
return Debug.failBadSyntaxKind(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined {
|
|
const promisedType = getPromisedTypeOfPromise(type, errorNode);
|
|
return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
|
|
}
|
|
|
|
/**
|
|
* Gets the "promised type" of a promise.
|
|
* @param type The type of the promise.
|
|
* @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback.
|
|
*/
|
|
function getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined {
|
|
//
|
|
// { // promise
|
|
// then( // thenFunction
|
|
// onfulfilled: ( // onfulfilledParameterType
|
|
// value: T // valueParameterType
|
|
// ) => any
|
|
// ): any;
|
|
// }
|
|
//
|
|
|
|
if (isTypeAny(promise)) {
|
|
return undefined;
|
|
}
|
|
|
|
const typeAsPromise = <PromiseOrAwaitableType>promise;
|
|
if (typeAsPromise.promisedTypeOfPromise) {
|
|
return typeAsPromise.promisedTypeOfPromise;
|
|
}
|
|
|
|
if (isReferenceToType(promise, getGlobalPromiseType(/*reportErrors*/ false))) {
|
|
return typeAsPromise.promisedTypeOfPromise = getTypeArguments(<GenericType>promise)[0];
|
|
}
|
|
|
|
const thenFunction = getTypeOfPropertyOfType(promise, "then" as __String)!; // TODO: GH#18217
|
|
if (isTypeAny(thenFunction)) {
|
|
return undefined;
|
|
}
|
|
|
|
const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray;
|
|
if (thenSignatures.length === 0) {
|
|
if (errorNode) {
|
|
error(errorNode, Diagnostics.A_promise_must_have_a_then_method);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(thenSignatures, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull);
|
|
if (isTypeAny(onfulfilledParameterType)) {
|
|
return undefined;
|
|
}
|
|
|
|
const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call);
|
|
if (onfulfilledParameterSignatures.length === 0) {
|
|
if (errorNode) {
|
|
error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype);
|
|
}
|
|
|
|
/**
|
|
* Gets the "awaited type" of a type.
|
|
* @param type The type to await.
|
|
* @remarks The "awaited type" of an expression is its "promised type" if the expression is a
|
|
* Promise-like type; otherwise, it is the type of the expression. This is used to reflect
|
|
* The runtime behavior of the `await` keyword.
|
|
*/
|
|
function checkAwaitedType(type: Type, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type {
|
|
const awaitedType = getAwaitedType(type, errorNode, diagnosticMessage, arg0);
|
|
return awaitedType || errorType;
|
|
}
|
|
|
|
function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined {
|
|
const typeAsAwaitable = <PromiseOrAwaitableType>type;
|
|
if (typeAsAwaitable.awaitedTypeOfType) {
|
|
return typeAsAwaitable.awaitedTypeOfType;
|
|
}
|
|
|
|
if (isTypeAny(type)) {
|
|
return typeAsAwaitable.awaitedTypeOfType = type;
|
|
}
|
|
|
|
if (type.flags & TypeFlags.Union) {
|
|
let types: Type[] | undefined;
|
|
for (const constituentType of (<UnionType>type).types) {
|
|
types = append<Type>(types, getAwaitedType(constituentType, errorNode, diagnosticMessage, arg0));
|
|
}
|
|
|
|
if (!types) {
|
|
return undefined;
|
|
}
|
|
|
|
return typeAsAwaitable.awaitedTypeOfType = getUnionType(types);
|
|
}
|
|
|
|
const promisedType = getPromisedTypeOfPromise(type);
|
|
if (promisedType) {
|
|
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
|
|
// Verify that we don't have a bad actor in the form of a promise whose
|
|
// promised type is the same as the promise type, or a mutually recursive
|
|
// promise. If so, we return undefined as we cannot guess the shape. If this
|
|
// were the actual case in the JavaScript, this Promise would never resolve.
|
|
//
|
|
// An example of a bad actor with a singly-recursive promise type might
|
|
// be:
|
|
//
|
|
// interface BadPromise {
|
|
// then(
|
|
// onfulfilled: (value: BadPromise) => any,
|
|
// onrejected: (error: any) => any): BadPromise;
|
|
// }
|
|
// The above interface will pass the PromiseLike check, and return a
|
|
// promised type of `BadPromise`. Since this is a self reference, we
|
|
// don't want to keep recursing ad infinitum.
|
|
//
|
|
// An example of a bad actor in the form of a mutually-recursive
|
|
// promise type might be:
|
|
//
|
|
// interface BadPromiseA {
|
|
// then(
|
|
// onfulfilled: (value: BadPromiseB) => any,
|
|
// onrejected: (error: any) => any): BadPromiseB;
|
|
// }
|
|
//
|
|
// interface BadPromiseB {
|
|
// then(
|
|
// onfulfilled: (value: BadPromiseA) => any,
|
|
// onrejected: (error: any) => any): BadPromiseA;
|
|
// }
|
|
//
|
|
if (errorNode) {
|
|
error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Keep track of the type we're about to unwrap to avoid bad recursive promise types.
|
|
// See the comments above for more information.
|
|
awaitedTypeStack.push(type.id);
|
|
const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
|
|
awaitedTypeStack.pop();
|
|
|
|
if (!awaitedType) {
|
|
return undefined;
|
|
}
|
|
|
|
return typeAsAwaitable.awaitedTypeOfType = awaitedType;
|
|
}
|
|
|
|
// The type was not a promise, so it could not be unwrapped any further.
|
|
// As long as the type does not have a callable "then" property, it is
|
|
// safe to return the type; otherwise, an error will be reported in
|
|
// the call to getNonThenableType and we will return undefined.
|
|
//
|
|
// An example of a non-promise "thenable" might be:
|
|
//
|
|
// await { then(): void {} }
|
|
//
|
|
// The "thenable" does not match the minimal definition for a promise. When
|
|
// a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise
|
|
// will never settle. We treat this as an error to help flag an early indicator
|
|
// of a runtime problem. If the user wants to return this value from an async
|
|
// function, they would need to wrap it in some other value. If they want it to
|
|
// be treated as a promise, they can cast to <any>.
|
|
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
|
|
if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) {
|
|
if (errorNode) {
|
|
if (!diagnosticMessage) return Debug.fail();
|
|
error(errorNode, diagnosticMessage, arg0);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
return typeAsAwaitable.awaitedTypeOfType = type;
|
|
}
|
|
|
|
/**
|
|
* Checks the return type of an async function to ensure it is a compatible
|
|
* Promise implementation.
|
|
*
|
|
* This checks that an async function has a valid Promise-compatible return type.
|
|
* An async function has a valid Promise-compatible return type if the resolved value
|
|
* of the return type has a construct signature that takes in an `initializer` function
|
|
* that in turn supplies a `resolve` function as one of its arguments and results in an
|
|
* object with a callable `then` signature.
|
|
*
|
|
* @param node The signature to check
|
|
*/
|
|
function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) {
|
|
// As part of our emit for an async function, we will need to emit the entity name of
|
|
// the return type annotation as an expression. To meet the necessary runtime semantics
|
|
// for __awaiter, we must also check that the type of the declaration (e.g. the static
|
|
// side or "constructor" of the promise type) is compatible `PromiseConstructorLike`.
|
|
//
|
|
// An example might be (from lib.es6.d.ts):
|
|
//
|
|
// interface Promise<T> { ... }
|
|
// interface PromiseConstructor {
|
|
// new <T>(...): Promise<T>;
|
|
// }
|
|
// declare var Promise: PromiseConstructor;
|
|
//
|
|
// When an async function declares a return type annotation of `Promise<T>`, we
|
|
// need to get the type of the `Promise` variable declaration above, which would
|
|
// be `PromiseConstructor`.
|
|
//
|
|
// The same case applies to a class:
|
|
//
|
|
// declare class Promise<T> {
|
|
// constructor(...);
|
|
// then<U>(...): Promise<U>;
|
|
// }
|
|
//
|
|
const returnType = getTypeFromTypeNode(returnTypeNode);
|
|
|
|
if (languageVersion >= ScriptTarget.ES2015) {
|
|
if (returnType === errorType) {
|
|
return;
|
|
}
|
|
const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true);
|
|
if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) {
|
|
// The promise type was not a valid type reference to the global promise type, so we
|
|
// report an error and return the unknown type.
|
|
error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type);
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
// Always mark the type node as referenced if it points to a value
|
|
markTypeNodeAsReferenced(returnTypeNode);
|
|
|
|
if (returnType === errorType) {
|
|
return;
|
|
}
|
|
|
|
const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode);
|
|
if (promiseConstructorName === undefined) {
|
|
error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType));
|
|
return;
|
|
}
|
|
|
|
const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true);
|
|
const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType;
|
|
if (promiseConstructorType === errorType) {
|
|
if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) {
|
|
error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option);
|
|
}
|
|
else {
|
|
error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName));
|
|
}
|
|
return;
|
|
}
|
|
|
|
const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true);
|
|
if (globalPromiseConstructorLikeType === emptyObjectType) {
|
|
// If we couldn't resolve the global PromiseConstructorLike type we cannot verify
|
|
// compatibility with __awaiter.
|
|
error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName));
|
|
return;
|
|
}
|
|
|
|
if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode,
|
|
Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) {
|
|
return;
|
|
}
|
|
|
|
// Verify there is no local declaration that could collide with the promise constructor.
|
|
const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName);
|
|
const collidingSymbol = getSymbol(node.locals!, rootName.escapedText, SymbolFlags.Value);
|
|
if (collidingSymbol) {
|
|
error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions,
|
|
idText(rootName),
|
|
entityNameToString(promiseConstructorName));
|
|
return;
|
|
}
|
|
}
|
|
checkAwaitedType(returnType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
|
|
}
|
|
|
|
/** Check a decorator */
|
|
function checkDecorator(node: Decorator): void {
|
|
const signature = getResolvedSignature(node);
|
|
const returnType = getReturnTypeOfSignature(signature);
|
|
if (returnType.flags & TypeFlags.Any) {
|
|
return;
|
|
}
|
|
|
|
let expectedReturnType: Type;
|
|
const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node);
|
|
let errorInfo: DiagnosticMessageChain | undefined;
|
|
switch (node.parent.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
const classSymbol = getSymbolOfNode(node.parent);
|
|
const classConstructorType = getTypeOfSymbol(classSymbol);
|
|
expectedReturnType = getUnionType([classConstructorType, voidType]);
|
|
break;
|
|
|
|
case SyntaxKind.Parameter:
|
|
expectedReturnType = voidType;
|
|
errorInfo = chainDiagnosticMessages(
|
|
/*details*/ undefined,
|
|
Diagnostics.The_return_type_of_a_parameter_decorator_function_must_be_either_void_or_any);
|
|
|
|
break;
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
expectedReturnType = voidType;
|
|
errorInfo = chainDiagnosticMessages(
|
|
/*details*/ undefined,
|
|
Diagnostics.The_return_type_of_a_property_decorator_function_must_be_either_void_or_any);
|
|
break;
|
|
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
const methodType = getTypeOfNode(node.parent);
|
|
const descriptorType = createTypedPropertyDescriptorType(methodType);
|
|
expectedReturnType = getUnionType([descriptorType, voidType]);
|
|
break;
|
|
|
|
default:
|
|
return Debug.fail();
|
|
}
|
|
|
|
checkTypeAssignableTo(
|
|
returnType,
|
|
expectedReturnType,
|
|
node,
|
|
headMessage,
|
|
() => errorInfo);
|
|
}
|
|
|
|
/**
|
|
* If a TypeNode can be resolved to a value symbol imported from an external module, it is
|
|
* marked as referenced to prevent import elision.
|
|
*/
|
|
function markTypeNodeAsReferenced(node: TypeNode) {
|
|
markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node));
|
|
}
|
|
|
|
function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined) {
|
|
if (!typeName) return;
|
|
|
|
const rootName = getFirstIdentifier(typeName);
|
|
const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias;
|
|
const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isRefernce*/ true);
|
|
if (rootSymbol
|
|
&& rootSymbol.flags & SymbolFlags.Alias
|
|
&& symbolIsValue(rootSymbol)
|
|
&& !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))
|
|
&& !getTypeOnlyAliasDeclaration(rootSymbol)) {
|
|
markAliasSymbolAsReferenced(rootSymbol);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function marks the type used for metadata decorator as referenced if it is import
|
|
* from external module.
|
|
* This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in
|
|
* union and intersection type
|
|
* @param node
|
|
*/
|
|
function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void {
|
|
const entityName = getEntityNameForDecoratorMetadata(node);
|
|
if (entityName && isEntityName(entityName)) {
|
|
markEntityNameOrEntityExpressionAsReference(entityName);
|
|
}
|
|
}
|
|
|
|
function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined {
|
|
if (node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.IntersectionType:
|
|
case SyntaxKind.UnionType:
|
|
return getEntityNameForDecoratorMetadataFromTypeList((<UnionOrIntersectionTypeNode>node).types);
|
|
|
|
case SyntaxKind.ConditionalType:
|
|
return getEntityNameForDecoratorMetadataFromTypeList([(<ConditionalTypeNode>node).trueType, (<ConditionalTypeNode>node).falseType]);
|
|
|
|
case SyntaxKind.ParenthesizedType:
|
|
return getEntityNameForDecoratorMetadata((<ParenthesizedTypeNode>node).type);
|
|
|
|
case SyntaxKind.TypeReference:
|
|
return (<TypeReferenceNode>node).typeName;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getEntityNameForDecoratorMetadataFromTypeList(types: readonly TypeNode[]): EntityName | undefined {
|
|
let commonEntityName: EntityName | undefined;
|
|
for (let typeNode of types) {
|
|
while (typeNode.kind === SyntaxKind.ParenthesizedType) {
|
|
typeNode = (typeNode as ParenthesizedTypeNode).type; // Skip parens if need be
|
|
}
|
|
if (typeNode.kind === SyntaxKind.NeverKeyword) {
|
|
continue; // Always elide `never` from the union/intersection if possible
|
|
}
|
|
if (!strictNullChecks && (typeNode.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) {
|
|
continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks
|
|
}
|
|
const individualEntityName = getEntityNameForDecoratorMetadata(typeNode);
|
|
if (!individualEntityName) {
|
|
// Individual is something like string number
|
|
// So it would be serialized to either that type or object
|
|
// Safe to return here
|
|
return undefined;
|
|
}
|
|
|
|
if (commonEntityName) {
|
|
// Note this is in sync with the transformation that happens for type node.
|
|
// Keep this in sync with serializeUnionOrIntersectionType
|
|
// Verify if they refer to same entity and is identifier
|
|
// return undefined if they dont match because we would emit object
|
|
if (!isIdentifier(commonEntityName) ||
|
|
!isIdentifier(individualEntityName) ||
|
|
commonEntityName.escapedText !== individualEntityName.escapedText) {
|
|
return undefined;
|
|
}
|
|
}
|
|
else {
|
|
commonEntityName = individualEntityName;
|
|
}
|
|
}
|
|
return commonEntityName;
|
|
}
|
|
|
|
function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined {
|
|
const typeNode = getEffectiveTypeAnnotationNode(node);
|
|
return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode;
|
|
}
|
|
|
|
/** Check the decorators of a node */
|
|
function checkDecorators(node: Node): void {
|
|
if (!node.decorators) {
|
|
return;
|
|
}
|
|
|
|
// skip this check for nodes that cannot have decorators. These should have already had an error reported by
|
|
// checkGrammarDecorators.
|
|
if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) {
|
|
return;
|
|
}
|
|
|
|
if (!compilerOptions.experimentalDecorators) {
|
|
error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning);
|
|
}
|
|
|
|
const firstDecorator = node.decorators[0];
|
|
checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate);
|
|
if (node.kind === SyntaxKind.Parameter) {
|
|
checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param);
|
|
}
|
|
|
|
if (compilerOptions.emitDecoratorMetadata) {
|
|
checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata);
|
|
|
|
// we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator.
|
|
switch (node.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
const constructor = getFirstConstructorWithBody(<ClassDeclaration>node);
|
|
if (constructor) {
|
|
for (const parameter of constructor.parameters) {
|
|
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor;
|
|
const otherAccessor = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(node as AccessorDeclaration), otherKind);
|
|
markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node as AccessorDeclaration) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor));
|
|
break;
|
|
case SyntaxKind.MethodDeclaration:
|
|
for (const parameter of (<FunctionLikeDeclaration>node).parameters) {
|
|
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter));
|
|
}
|
|
|
|
markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(<FunctionLikeDeclaration>node));
|
|
break;
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(<ParameterDeclaration>node));
|
|
break;
|
|
|
|
case SyntaxKind.Parameter:
|
|
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(<ParameterDeclaration>node));
|
|
const containingSignature = (node as ParameterDeclaration).parent;
|
|
for (const parameter of containingSignature.parameters) {
|
|
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
forEach(node.decorators, checkDecorator);
|
|
}
|
|
|
|
function checkFunctionDeclaration(node: FunctionDeclaration): void {
|
|
if (produceDiagnostics) {
|
|
checkFunctionOrMethodDeclaration(node);
|
|
checkGrammarForGenerator(node);
|
|
checkCollisionWithRequireExportsInGeneratedCode(node, node.name!);
|
|
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!);
|
|
}
|
|
}
|
|
|
|
function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) {
|
|
if (!node.typeExpression) {
|
|
// If the node had `@property` tags, `typeExpression` would have been set to the first property tag.
|
|
error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags);
|
|
}
|
|
|
|
if (node.name) {
|
|
checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0);
|
|
}
|
|
checkSourceElement(node.typeExpression);
|
|
}
|
|
|
|
function checkJSDocTemplateTag(node: JSDocTemplateTag): void {
|
|
checkSourceElement(node.constraint);
|
|
for (const tp of node.typeParameters) {
|
|
checkSourceElement(tp);
|
|
}
|
|
}
|
|
|
|
function checkJSDocTypeTag(node: JSDocTypeTag) {
|
|
checkSourceElement(node.typeExpression);
|
|
}
|
|
|
|
function checkJSDocParameterTag(node: JSDocParameterTag) {
|
|
checkSourceElement(node.typeExpression);
|
|
if (!getParameterSymbolFromJSDoc(node)) {
|
|
const decl = getHostSignatureFromJSDoc(node);
|
|
// don't issue an error for invalid hosts -- just functions --
|
|
// and give a better error message when the host function mentions `arguments`
|
|
// but the tag doesn't have an array type
|
|
if (decl) {
|
|
const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node);
|
|
if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) {
|
|
return;
|
|
}
|
|
if (!containsArgumentsReference(decl)) {
|
|
if (isQualifiedName(node.name)) {
|
|
error(node.name,
|
|
Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1,
|
|
entityNameToString(node.name),
|
|
entityNameToString(node.name.left));
|
|
}
|
|
else {
|
|
error(node.name,
|
|
Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name,
|
|
idText(node.name));
|
|
}
|
|
}
|
|
else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node &&
|
|
node.typeExpression && node.typeExpression.type &&
|
|
!isArrayType(getTypeFromTypeNode(node.typeExpression.type))) {
|
|
error(node.name,
|
|
Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type,
|
|
idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkJSDocFunctionType(node: JSDocFunctionType): void {
|
|
if (produceDiagnostics && !node.type && !isJSDocConstructSignature(node)) {
|
|
reportImplicitAny(node, anyType);
|
|
}
|
|
checkSignatureDeclaration(node);
|
|
}
|
|
|
|
function checkJSDocImplementsTag(node: JSDocImplementsTag): void {
|
|
const classLike = getEffectiveJSDocHost(node);
|
|
if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) {
|
|
error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName));
|
|
}
|
|
}
|
|
function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void {
|
|
const classLike = getEffectiveJSDocHost(node);
|
|
if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) {
|
|
error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName));
|
|
return;
|
|
}
|
|
|
|
const augmentsTags = getJSDocTags(classLike).filter(isJSDocAugmentsTag);
|
|
Debug.assert(augmentsTags.length > 0);
|
|
if (augmentsTags.length > 1) {
|
|
error(augmentsTags[1], Diagnostics.Class_declarations_cannot_have_more_than_one_augments_or_extends_tag);
|
|
}
|
|
|
|
const name = getIdentifierFromEntityNameExpression(node.class.expression);
|
|
const extend = getClassExtendsHeritageElement(classLike);
|
|
if (extend) {
|
|
const className = getIdentifierFromEntityNameExpression(extend.expression);
|
|
if (className && name.escapedText !== className.escapedText) {
|
|
error(name, Diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, idText(node.tagName), idText(name), idText(className));
|
|
}
|
|
}
|
|
}
|
|
|
|
function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateIdentifier;
|
|
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined;
|
|
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return node as Identifier;
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
return (node as PropertyAccessExpression).name;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void {
|
|
checkDecorators(node);
|
|
checkSignatureDeclaration(node);
|
|
const functionFlags = getFunctionFlags(node);
|
|
|
|
// Do not use hasDynamicName here, because that returns false for well known symbols.
|
|
// We want to perform checkComputedPropertyName for all computed properties, including
|
|
// well known symbols.
|
|
if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) {
|
|
// This check will account for methods in class/interface declarations,
|
|
// as well as accessors in classes/object literals
|
|
checkComputedPropertyName(node.name);
|
|
}
|
|
|
|
if (!hasNonBindableDynamicName(node)) {
|
|
// first we want to check the local symbol that contain this declaration
|
|
// - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol
|
|
// - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode
|
|
const symbol = getSymbolOfNode(node);
|
|
const localSymbol = node.localSymbol || symbol;
|
|
|
|
// Since the javascript won't do semantic analysis like typescript,
|
|
// if the javascript file comes before the typescript file and both contain same name functions,
|
|
// checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function.
|
|
const firstDeclaration = find(localSymbol.declarations,
|
|
// Get first non javascript function declaration
|
|
declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile));
|
|
|
|
// Only type check the symbol once
|
|
if (node === firstDeclaration) {
|
|
checkFunctionOrConstructorSymbol(localSymbol);
|
|
}
|
|
|
|
if (symbol.parent) {
|
|
// run check once for the first declaration
|
|
if (getDeclarationOfKind(symbol, node.kind) === node) {
|
|
// run check on export symbol to check that modifiers agree across all exported declarations
|
|
checkFunctionOrConstructorSymbol(symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body;
|
|
checkSourceElement(body);
|
|
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node));
|
|
|
|
if (produceDiagnostics && !getEffectiveReturnTypeNode(node)) {
|
|
// Report an implicit any error if there is no body, no explicit return type, and node is not a private method
|
|
// in an ambient context
|
|
if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) {
|
|
reportImplicitAny(node, anyType);
|
|
}
|
|
|
|
if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) {
|
|
// A generator with a body and no type annotation can still cause errors. It can error if the
|
|
// yielded values have no common supertype, or it can give an implicit any error if it has no
|
|
// yielded values. The only way to trigger these errors is to try checking its return type.
|
|
getReturnTypeOfSignature(getSignatureFromDeclaration(node));
|
|
}
|
|
}
|
|
|
|
// A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature
|
|
if (isInJSFile(node)) {
|
|
const typeTag = getJSDocTypeTag(node);
|
|
if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) {
|
|
error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature);
|
|
}
|
|
}
|
|
}
|
|
|
|
function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void {
|
|
// May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`.
|
|
if (produceDiagnostics) {
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path);
|
|
if (!potentiallyUnusedIdentifiers) {
|
|
potentiallyUnusedIdentifiers = [];
|
|
allPotentiallyUnusedIdentifiers.set(sourceFile.path, potentiallyUnusedIdentifiers);
|
|
}
|
|
// TODO: GH#22580
|
|
// Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice");
|
|
potentiallyUnusedIdentifiers.push(node);
|
|
}
|
|
}
|
|
|
|
type PotentiallyUnusedIdentifier =
|
|
| SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration
|
|
| Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement
|
|
| Exclude<SignatureDeclaration, IndexSignatureDeclaration | JSDocFunctionType> | TypeAliasDeclaration
|
|
| InferTypeNode;
|
|
|
|
function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: readonly PotentiallyUnusedIdentifier[], addDiagnostic: AddUnusedDiagnostic) {
|
|
for (const node of potentiallyUnusedIdentifiers) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
checkUnusedClassMembers(node, addDiagnostic);
|
|
checkUnusedTypeParameters(node, addDiagnostic);
|
|
break;
|
|
case SyntaxKind.SourceFile:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.Block:
|
|
case SyntaxKind.CaseBlock:
|
|
case SyntaxKind.ForStatement:
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
checkUnusedLocalsAndParameters(node, addDiagnostic);
|
|
break;
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
if (node.body) { // Don't report unused parameters in overloads
|
|
checkUnusedLocalsAndParameters(node, addDiagnostic);
|
|
}
|
|
checkUnusedTypeParameters(node, addDiagnostic);
|
|
break;
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
checkUnusedTypeParameters(node, addDiagnostic);
|
|
break;
|
|
case SyntaxKind.InferType:
|
|
checkUnusedInferTypeParameter(node, addDiagnostic);
|
|
break;
|
|
default:
|
|
Debug.assertNever(node, "Node should not have been registered for unused identifiers check");
|
|
}
|
|
}
|
|
}
|
|
|
|
function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) {
|
|
const node = getNameOfDeclaration(declaration) || declaration;
|
|
const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read;
|
|
addDiagnostic(declaration, UnusedKind.Local, createDiagnosticForNode(node, message, name));
|
|
}
|
|
|
|
function isIdentifierThatStartsWithUnderscore(node: Node) {
|
|
return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._;
|
|
}
|
|
|
|
function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void {
|
|
for (const member of node.members) {
|
|
switch (member.kind) {
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) {
|
|
// Already would have reported an error on the getter.
|
|
break;
|
|
}
|
|
const symbol = getSymbolOfNode(member);
|
|
if (!symbol.isReferenced
|
|
&& (hasModifier(member, ModifierFlags.Private) || isNamedDeclaration(member) && isPrivateIdentifier(member.name))
|
|
&& !(member.flags & NodeFlags.Ambient)) {
|
|
addDiagnostic(member, UnusedKind.Local, createDiagnosticForNode(member.name!, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol)));
|
|
}
|
|
break;
|
|
case SyntaxKind.Constructor:
|
|
for (const parameter of (<ConstructorDeclaration>member).parameters) {
|
|
if (!parameter.symbol.isReferenced && hasModifier(parameter, ModifierFlags.Private)) {
|
|
addDiagnostic(parameter, UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol)));
|
|
}
|
|
}
|
|
break;
|
|
case SyntaxKind.IndexSignature:
|
|
case SyntaxKind.SemicolonClassElement:
|
|
// Can't be private
|
|
break;
|
|
default:
|
|
Debug.fail();
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkUnusedInferTypeParameter(node: InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void {
|
|
const { typeParameter } = node;
|
|
if (isTypeParameterUnused(typeParameter)) {
|
|
addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name)));
|
|
}
|
|
}
|
|
|
|
function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void {
|
|
// Only report errors on the last declaration for the type parameter container;
|
|
// this ensures that all uses have been accounted for.
|
|
if (last(getSymbolOfNode(node).declarations) !== node) return;
|
|
|
|
const typeParameters = getEffectiveTypeParameterDeclarations(node);
|
|
const seenParentsWithEveryUnused = new NodeSet<DeclarationWithTypeParameterChildren>();
|
|
|
|
for (const typeParameter of typeParameters) {
|
|
if (!isTypeParameterUnused(typeParameter)) continue;
|
|
|
|
const name = idText(typeParameter.name);
|
|
const { parent } = typeParameter;
|
|
if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) {
|
|
if (seenParentsWithEveryUnused.tryAdd(parent)) {
|
|
const range = isJSDocTemplateTag(parent)
|
|
// Whole @template tag
|
|
? rangeOfNode(parent)
|
|
// Include the `<>` in the error message
|
|
: rangeOfTypeParameters(parent.typeParameters!);
|
|
const only = typeParameters.length === 1;
|
|
const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused;
|
|
const arg0 = only ? name : undefined;
|
|
addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(getSourceFileOfNode(parent), range.pos, range.end - range.pos, message, arg0));
|
|
}
|
|
}
|
|
else {
|
|
addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name));
|
|
}
|
|
}
|
|
}
|
|
function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean {
|
|
return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name);
|
|
}
|
|
|
|
function addToGroup<K, V>(map: Map<[K, V[]]>, key: K, value: V, getKey: (key: K) => number | string): void {
|
|
const keyString = String(getKey(key));
|
|
const group = map.get(keyString);
|
|
if (group) {
|
|
group[1].push(value);
|
|
}
|
|
else {
|
|
map.set(keyString, [key, [value]]);
|
|
}
|
|
}
|
|
|
|
function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined {
|
|
return tryCast(getRootDeclaration(node), isParameter);
|
|
}
|
|
|
|
function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void {
|
|
// Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value.
|
|
const unusedImports = createMap<[ImportClause, ImportedDeclaration[]]>();
|
|
const unusedDestructures = createMap<[ObjectBindingPattern, BindingElement[]]>();
|
|
const unusedVariables = createMap<[VariableDeclarationList, VariableDeclaration[]]>();
|
|
nodeWithLocals.locals!.forEach(local => {
|
|
// If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`.
|
|
// If it's a type parameter merged with a parameter, check if the parameter-side is used.
|
|
if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !(local.isReferenced! & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) {
|
|
return;
|
|
}
|
|
|
|
for (const declaration of local.declarations) {
|
|
if (isAmbientModule(declaration) ||
|
|
(isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!)) {
|
|
continue;
|
|
}
|
|
|
|
if (isImportedDeclaration(declaration)) {
|
|
addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId);
|
|
}
|
|
else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) {
|
|
// In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though.
|
|
const lastElement = last(declaration.parent.elements);
|
|
if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) {
|
|
addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId);
|
|
}
|
|
}
|
|
else if (isVariableDeclaration(declaration)) {
|
|
addToGroup(unusedVariables, declaration.parent, declaration, getNodeId);
|
|
}
|
|
else {
|
|
const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration);
|
|
const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration);
|
|
if (parameter && name) {
|
|
if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) {
|
|
addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local)));
|
|
}
|
|
}
|
|
else {
|
|
errorUnusedLocal(declaration, symbolName(local), addDiagnostic);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
unusedImports.forEach(([importClause, unuseds]) => {
|
|
const importDecl = importClause.parent;
|
|
const nDeclarations = (importClause.name ? 1 : 0) +
|
|
(importClause.namedBindings ?
|
|
(importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length)
|
|
: 0);
|
|
if (nDeclarations === unuseds.length) {
|
|
addDiagnostic(importDecl, UnusedKind.Local, unuseds.length === 1
|
|
? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name!))
|
|
: createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused));
|
|
}
|
|
else {
|
|
for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name!), addDiagnostic);
|
|
}
|
|
});
|
|
unusedDestructures.forEach(([bindingPattern, bindingElements]) => {
|
|
const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local;
|
|
if (bindingPattern.elements.length === bindingElements.length) {
|
|
if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) {
|
|
addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId);
|
|
}
|
|
else {
|
|
addDiagnostic(bindingPattern, kind, bindingElements.length === 1
|
|
? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(bindingElements).name))
|
|
: createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused));
|
|
}
|
|
}
|
|
else {
|
|
for (const e of bindingElements) {
|
|
addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.name)));
|
|
}
|
|
}
|
|
});
|
|
unusedVariables.forEach(([declarationList, declarations]) => {
|
|
if (declarationList.declarations.length === declarations.length) {
|
|
addDiagnostic(declarationList, UnusedKind.Local, declarations.length === 1
|
|
? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name))
|
|
: createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused));
|
|
}
|
|
else {
|
|
for (const decl of declarations) {
|
|
addDiagnostic(decl, UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(decl.name)));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function bindingNameText(name: BindingName): string {
|
|
switch (name.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return idText(name);
|
|
case SyntaxKind.ArrayBindingPattern:
|
|
case SyntaxKind.ObjectBindingPattern:
|
|
return bindingNameText(cast(first(name.elements), isBindingElement).name);
|
|
default:
|
|
return Debug.assertNever(name);
|
|
}
|
|
}
|
|
|
|
type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport;
|
|
function isImportedDeclaration(node: Node): node is ImportedDeclaration {
|
|
return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport;
|
|
}
|
|
function importClauseFromImported(decl: ImportedDeclaration): ImportClause {
|
|
return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent;
|
|
}
|
|
|
|
function checkBlock(node: Block) {
|
|
// Grammar checking for SyntaxKind.Block
|
|
if (node.kind === SyntaxKind.Block) {
|
|
checkGrammarStatementInAmbientContext(node);
|
|
}
|
|
if (isFunctionOrModuleBlock(node)) {
|
|
const saveFlowAnalysisDisabled = flowAnalysisDisabled;
|
|
forEach(node.statements, checkSourceElement);
|
|
flowAnalysisDisabled = saveFlowAnalysisDisabled;
|
|
}
|
|
else {
|
|
forEach(node.statements, checkSourceElement);
|
|
}
|
|
if (node.locals) {
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
}
|
|
|
|
function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) {
|
|
// no rest parameters \ declaration context \ overload - no codegen impact
|
|
if (languageVersion >= ScriptTarget.ES2015 || compilerOptions.noEmit || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((<FunctionLikeDeclaration>node).body)) {
|
|
return;
|
|
}
|
|
|
|
forEach(node.parameters, p => {
|
|
if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) {
|
|
error(p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters);
|
|
}
|
|
});
|
|
}
|
|
|
|
function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean {
|
|
if (!(identifier && identifier.escapedText === name)) {
|
|
return false;
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.PropertyDeclaration ||
|
|
node.kind === SyntaxKind.PropertySignature ||
|
|
node.kind === SyntaxKind.MethodDeclaration ||
|
|
node.kind === SyntaxKind.MethodSignature ||
|
|
node.kind === SyntaxKind.GetAccessor ||
|
|
node.kind === SyntaxKind.SetAccessor) {
|
|
// it is ok to have member named '_super' or '_this' - member access is always qualified
|
|
return false;
|
|
}
|
|
|
|
if (node.flags & NodeFlags.Ambient) {
|
|
// ambient context - no codegen impact
|
|
return false;
|
|
}
|
|
|
|
const root = getRootDeclaration(node);
|
|
if (root.kind === SyntaxKind.Parameter && nodeIsMissing((<FunctionLikeDeclaration>root.parent).body)) {
|
|
// just an overload - no codegen impact
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// this function will run after checking the source file so 'CaptureThis' is correct for all nodes
|
|
function checkIfThisIsCapturedInEnclosingScope(node: Node): void {
|
|
findAncestor(node, current => {
|
|
if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) {
|
|
const isDeclaration = node.kind !== SyntaxKind.Identifier;
|
|
if (isDeclaration) {
|
|
error(getNameOfDeclaration(<Declaration>node), Diagnostics.Duplicate_identifier_this_Compiler_uses_variable_declaration_this_to_capture_this_reference);
|
|
}
|
|
else {
|
|
error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void {
|
|
findAncestor(node, current => {
|
|
if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) {
|
|
const isDeclaration = node.kind !== SyntaxKind.Identifier;
|
|
if (isDeclaration) {
|
|
error(getNameOfDeclaration(<Declaration>node), Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference);
|
|
}
|
|
else {
|
|
error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
function checkWeakMapCollision(node: Node) {
|
|
const enclosingBlockScope = getEnclosingBlockScopeContainer(node);
|
|
if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) {
|
|
error(node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, "WeakMap");
|
|
}
|
|
}
|
|
|
|
function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier) {
|
|
// No need to check for require or exports for ES6 modules and later
|
|
if (moduleKind >= ModuleKind.ES2015 || compilerOptions.noEmit) {
|
|
return;
|
|
}
|
|
|
|
if (!needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) {
|
|
return;
|
|
}
|
|
|
|
// Uninstantiated modules shouldnt do this check
|
|
if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) {
|
|
return;
|
|
}
|
|
|
|
// In case of variable declaration, node.parent is variable statement so look at the variable statement's parent
|
|
const parent = getDeclarationContainer(node);
|
|
if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>parent)) {
|
|
// If the declaration happens to be in external module, report error that require and exports are reserved keywords
|
|
error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module,
|
|
declarationNameToString(name), declarationNameToString(name));
|
|
}
|
|
}
|
|
|
|
function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void {
|
|
if (languageVersion >= ScriptTarget.ES2017 || compilerOptions.noEmit || !needCollisionCheckForIdentifier(node, name, "Promise")) {
|
|
return;
|
|
}
|
|
|
|
// Uninstantiated modules shouldnt do this check
|
|
if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) {
|
|
return;
|
|
}
|
|
|
|
// In case of variable declaration, node.parent is variable statement so look at the variable statement's parent
|
|
const parent = getDeclarationContainer(node);
|
|
if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>parent) && parent.flags & NodeFlags.HasAsyncFunctions) {
|
|
// If the declaration happens to be in external module, report error that Promise is a reserved identifier.
|
|
error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions,
|
|
declarationNameToString(name), declarationNameToString(name));
|
|
}
|
|
}
|
|
|
|
function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) {
|
|
// - ScriptBody : StatementList
|
|
// It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList
|
|
// also occurs in the VarDeclaredNames of StatementList.
|
|
|
|
// - Block : { StatementList }
|
|
// It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList
|
|
// also occurs in the VarDeclaredNames of StatementList.
|
|
|
|
// Variable declarations are hoisted to the top of their function scope. They can shadow
|
|
// block scoped declarations, which bind tighter. this will not be flagged as duplicate definition
|
|
// by the binder as the declaration scope is different.
|
|
// A non-initialized declaration is a no-op as the block declaration will resolve before the var
|
|
// declaration. the problem is if the declaration has an initializer. this will act as a write to the
|
|
// block declared value. this is fine for let, but not const.
|
|
// Only consider declarations with initializers, uninitialized const declarations will not
|
|
// step on a let/const variable.
|
|
// Do not consider const and const declarations, as duplicate block-scoped declarations
|
|
// are handled by the binder.
|
|
// We are only looking for const declarations that step on let\const declarations from a
|
|
// different scope. e.g.:
|
|
// {
|
|
// const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration
|
|
// const x = 0; // symbol for this declaration will be 'symbol'
|
|
// }
|
|
|
|
// skip block-scoped variables and parameters
|
|
if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) {
|
|
return;
|
|
}
|
|
|
|
// skip variable declarations that don't have initializers
|
|
// NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern
|
|
// so we'll always treat binding elements as initialized
|
|
if (node.kind === SyntaxKind.VariableDeclaration && !node.initializer) {
|
|
return;
|
|
}
|
|
|
|
const symbol = getSymbolOfNode(node);
|
|
if (symbol.flags & SymbolFlags.FunctionScopedVariable) {
|
|
if (!isIdentifier(node.name)) return Debug.fail();
|
|
const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false);
|
|
if (localDeclarationSymbol &&
|
|
localDeclarationSymbol !== symbol &&
|
|
localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) {
|
|
if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) {
|
|
const varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!;
|
|
const container =
|
|
varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent
|
|
? varDeclList.parent.parent
|
|
: undefined;
|
|
|
|
// names of block-scoped and function scoped variables can collide only
|
|
// if block scoped variable is defined in the function\module\source file scope (because of variable hoisting)
|
|
const namesShareScope =
|
|
container &&
|
|
(container.kind === SyntaxKind.Block && isFunctionLike(container.parent) ||
|
|
container.kind === SyntaxKind.ModuleBlock ||
|
|
container.kind === SyntaxKind.ModuleDeclaration ||
|
|
container.kind === SyntaxKind.SourceFile);
|
|
|
|
// here we know that function scoped variable is shadowed by block scoped one
|
|
// if they are defined in the same scope - binder has already reported redeclaration error
|
|
// otherwise if variable has an initializer - show error that initialization will fail
|
|
// since LHS will be block scoped name instead of function scoped
|
|
if (!namesShareScope) {
|
|
const name = symbolToString(localDeclarationSymbol);
|
|
error(node, Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function convertAutoToAny(type: Type) {
|
|
return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type;
|
|
}
|
|
|
|
// Check variable, parameter, or property declaration
|
|
function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) {
|
|
checkDecorators(node);
|
|
if (!isBindingElement(node)) {
|
|
checkSourceElement(node.type);
|
|
}
|
|
|
|
// JSDoc `function(string, string): string` syntax results in parameters with no name
|
|
if (!node.name) {
|
|
return;
|
|
}
|
|
// For a computed property, just check the initializer and exit
|
|
// Do not use hasDynamicName here, because that returns false for well known symbols.
|
|
// We want to perform checkComputedPropertyName for all computed properties, including
|
|
// well known symbols.
|
|
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
|
|
checkComputedPropertyName(node.name);
|
|
if (node.initializer) {
|
|
checkExpressionCached(node.initializer);
|
|
}
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.BindingElement) {
|
|
if (node.parent.kind === SyntaxKind.ObjectBindingPattern && languageVersion < ScriptTarget.ESNext) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest);
|
|
}
|
|
// check computed properties inside property names of binding elements
|
|
if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) {
|
|
checkComputedPropertyName(node.propertyName);
|
|
}
|
|
|
|
// check private/protected variable access
|
|
const parent = node.parent.parent;
|
|
const parentType = getTypeForBindingElementParent(parent);
|
|
const name = node.propertyName || node.name;
|
|
if (parentType && !isBindingPattern(name)) {
|
|
const exprType = getLiteralTypeFromPropertyName(name);
|
|
if (isTypeUsableAsPropertyName(exprType)) {
|
|
const nameText = getPropertyNameFromType(exprType);
|
|
const property = getPropertyOfType(parentType, nameText);
|
|
if (property) {
|
|
markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference.
|
|
checkPropertyAccessibility(parent, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType, property);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For a binding pattern, check contained binding elements
|
|
if (isBindingPattern(node.name)) {
|
|
if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.Read);
|
|
}
|
|
|
|
forEach(node.name.elements, checkSourceElement);
|
|
}
|
|
// For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body
|
|
if (node.initializer && getRootDeclaration(node).kind === SyntaxKind.Parameter && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) {
|
|
error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation);
|
|
return;
|
|
}
|
|
// For a binding pattern, validate the initializer and exit
|
|
if (isBindingPattern(node.name)) {
|
|
const needCheckInitializer = node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement;
|
|
const needCheckWidenedType = node.name.elements.length === 0;
|
|
if (needCheckInitializer || needCheckWidenedType) {
|
|
// Don't validate for-in initializer as it is already an error
|
|
const widenedType = getWidenedTypeForVariableLikeDeclaration(node);
|
|
if (needCheckInitializer) {
|
|
const initializerType = checkExpressionCached(node.initializer!);
|
|
if (strictNullChecks && needCheckWidenedType) {
|
|
checkNonNullNonVoidType(initializerType, node);
|
|
}
|
|
else {
|
|
checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer);
|
|
}
|
|
}
|
|
// check the binding pattern with empty elements
|
|
if (needCheckWidenedType) {
|
|
if (isArrayBindingPattern(node.name)) {
|
|
checkIteratedTypeOrElementType(IterationUse.Destructuring, widenedType, undefinedType, node);
|
|
}
|
|
else if (strictNullChecks) {
|
|
checkNonNullNonVoidType(widenedType, node);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
const symbol = getSymbolOfNode(node);
|
|
const type = convertAutoToAny(getTypeOfSymbol(symbol));
|
|
if (node === symbol.valueDeclaration) {
|
|
// Node is the primary declaration of the symbol, just validate the initializer
|
|
// Don't validate for-in initializer as it is already an error
|
|
const initializer = getEffectiveInitializer(node);
|
|
if (initializer) {
|
|
const isJSObjectLiteralInitializer = isInJSFile(node) &&
|
|
isObjectLiteralExpression(initializer) &&
|
|
(initializer.properties.length === 0 || isPrototypeAccess(node.name)) &&
|
|
hasEntries(symbol.exports);
|
|
if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) {
|
|
checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined);
|
|
}
|
|
}
|
|
if (symbol.declarations.length > 1) {
|
|
if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) {
|
|
error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Node is a secondary declaration, check that type is identical to primary declaration and check that
|
|
// initializer is consistent with type associated with the node
|
|
const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node));
|
|
|
|
if (type !== errorType && declarationType !== errorType &&
|
|
!isTypeIdenticalTo(type, declarationType) &&
|
|
!(symbol.flags & SymbolFlags.Assignment)) {
|
|
errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType);
|
|
}
|
|
if (node.initializer) {
|
|
checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined);
|
|
}
|
|
if (!areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) {
|
|
error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name));
|
|
}
|
|
}
|
|
if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) {
|
|
// We know we don't have a binding pattern or computed name here
|
|
checkExportsOnMergedDeclarations(node);
|
|
if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) {
|
|
checkVarDeclaredNamesNotShadowed(node);
|
|
}
|
|
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
|
|
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
|
|
if (!compilerOptions.noEmit && languageVersion < ScriptTarget.ESNext && needCollisionCheckForIdentifier(node, node.name, "WeakMap")) {
|
|
potentialWeakMapCollisions.push(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstDeclaration: Declaration | undefined, firstType: Type, nextDeclaration: Declaration, nextType: Type): void {
|
|
const nextDeclarationName = getNameOfDeclaration(nextDeclaration);
|
|
const message = nextDeclaration.kind === SyntaxKind.PropertyDeclaration || nextDeclaration.kind === SyntaxKind.PropertySignature
|
|
? Diagnostics.Subsequent_property_declarations_must_have_the_same_type_Property_0_must_be_of_type_1_but_here_has_type_2
|
|
: Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2;
|
|
const declName = declarationNameToString(nextDeclarationName);
|
|
const err = error(
|
|
nextDeclarationName,
|
|
message,
|
|
declName,
|
|
typeToString(firstType),
|
|
typeToString(nextType)
|
|
);
|
|
if (firstDeclaration) {
|
|
addRelatedInfo(err,
|
|
createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName)
|
|
);
|
|
}
|
|
}
|
|
|
|
function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) {
|
|
if ((left.kind === SyntaxKind.Parameter && right.kind === SyntaxKind.VariableDeclaration) ||
|
|
(left.kind === SyntaxKind.VariableDeclaration && right.kind === SyntaxKind.Parameter)) {
|
|
// Differences in optionality between parameters and variables are allowed.
|
|
return true;
|
|
}
|
|
|
|
if (hasQuestionToken(left) !== hasQuestionToken(right)) {
|
|
return false;
|
|
}
|
|
|
|
const interestingFlags = ModifierFlags.Private |
|
|
ModifierFlags.Protected |
|
|
ModifierFlags.Async |
|
|
ModifierFlags.Abstract |
|
|
ModifierFlags.Readonly |
|
|
ModifierFlags.Static;
|
|
|
|
return getSelectedModifierFlags(left, interestingFlags) === getSelectedModifierFlags(right, interestingFlags);
|
|
}
|
|
|
|
function checkVariableDeclaration(node: VariableDeclaration) {
|
|
checkGrammarVariableDeclaration(node);
|
|
return checkVariableLikeDeclaration(node);
|
|
}
|
|
|
|
function checkBindingElement(node: BindingElement) {
|
|
checkGrammarBindingElement(node);
|
|
return checkVariableLikeDeclaration(node);
|
|
}
|
|
|
|
function checkVariableStatement(node: VariableStatement) {
|
|
// Grammar checking
|
|
if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node);
|
|
forEach(node.declarationList.declarations, checkSourceElement);
|
|
}
|
|
|
|
function checkExpressionStatement(node: ExpressionStatement) {
|
|
// Grammar checking
|
|
checkGrammarStatementInAmbientContext(node);
|
|
|
|
checkExpression(node.expression);
|
|
}
|
|
|
|
function checkIfStatement(node: IfStatement) {
|
|
// Grammar checking
|
|
checkGrammarStatementInAmbientContext(node);
|
|
const type = checkTruthinessExpression(node.expression);
|
|
checkTestingKnownTruthyCallableType(node.expression, node.thenStatement, type);
|
|
checkSourceElement(node.thenStatement);
|
|
|
|
if (node.thenStatement.kind === SyntaxKind.EmptyStatement) {
|
|
error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement);
|
|
}
|
|
|
|
checkSourceElement(node.elseStatement);
|
|
}
|
|
|
|
function checkTestingKnownTruthyCallableType(condExpr: Expression, body: Statement | Expression, type: Type) {
|
|
if (!strictNullChecks) {
|
|
return;
|
|
}
|
|
|
|
const testedNode = isIdentifier(condExpr)
|
|
? condExpr
|
|
: isPropertyAccessExpression(condExpr)
|
|
? condExpr.name
|
|
: undefined;
|
|
|
|
if (!testedNode) {
|
|
return;
|
|
}
|
|
|
|
const possiblyFalsy = getFalsyFlags(type);
|
|
if (possiblyFalsy) {
|
|
return;
|
|
}
|
|
|
|
// While it technically should be invalid for any known-truthy value
|
|
// to be tested, we de-scope to functions unrefenced in the block as a
|
|
// heuristic to identify the most common bugs. There are too many
|
|
// false positives for values sourced from type definitions without
|
|
// strictNullChecks otherwise.
|
|
const callSignatures = getSignaturesOfType(type, SignatureKind.Call);
|
|
if (callSignatures.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const testedFunctionSymbol = getSymbolAtLocation(testedNode);
|
|
if (!testedFunctionSymbol) {
|
|
return;
|
|
}
|
|
|
|
const functionIsUsedInBody = forEachChild(body, function check(childNode): boolean | undefined {
|
|
if (isIdentifier(childNode)) {
|
|
const childSymbol = getSymbolAtLocation(childNode);
|
|
if (childSymbol && childSymbol.id === testedFunctionSymbol.id) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return forEachChild(childNode, check);
|
|
});
|
|
|
|
if (!functionIsUsedInBody) {
|
|
error(condExpr, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead);
|
|
}
|
|
}
|
|
|
|
function checkDoStatement(node: DoStatement) {
|
|
// Grammar checking
|
|
checkGrammarStatementInAmbientContext(node);
|
|
|
|
checkSourceElement(node.statement);
|
|
checkTruthinessExpression(node.expression);
|
|
}
|
|
|
|
function checkWhileStatement(node: WhileStatement) {
|
|
// Grammar checking
|
|
checkGrammarStatementInAmbientContext(node);
|
|
|
|
checkTruthinessExpression(node.expression);
|
|
checkSourceElement(node.statement);
|
|
}
|
|
|
|
function checkTruthinessOfType(type: Type, node: Node) {
|
|
if (type.flags & TypeFlags.Void) {
|
|
error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) {
|
|
return checkTruthinessOfType(checkExpression(node, checkMode), node);
|
|
}
|
|
|
|
function checkForStatement(node: ForStatement) {
|
|
// Grammar checking
|
|
if (!checkGrammarStatementInAmbientContext(node)) {
|
|
if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
checkGrammarVariableDeclarationList(<VariableDeclarationList>node.initializer);
|
|
}
|
|
}
|
|
|
|
if (node.initializer) {
|
|
if (node.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
forEach((<VariableDeclarationList>node.initializer).declarations, checkVariableDeclaration);
|
|
}
|
|
else {
|
|
checkExpression(node.initializer);
|
|
}
|
|
}
|
|
|
|
if (node.condition) checkTruthinessExpression(node.condition);
|
|
if (node.incrementor) checkExpression(node.incrementor);
|
|
checkSourceElement(node.statement);
|
|
if (node.locals) {
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
}
|
|
|
|
function checkForOfStatement(node: ForOfStatement): void {
|
|
checkGrammarForInOrForOfStatement(node);
|
|
|
|
if (node.awaitModifier) {
|
|
const functionFlags = getFunctionFlags(getContainingFunction(node));
|
|
if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < ScriptTarget.ESNext) {
|
|
// for..await..of in an async function or async generator function prior to ESNext requires the __asyncValues helper
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.ForAwaitOfIncludes);
|
|
}
|
|
}
|
|
else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) {
|
|
// for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes);
|
|
}
|
|
|
|
// Check the LHS and RHS
|
|
// If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS
|
|
// via checkRightHandSideOfForOf.
|
|
// If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference.
|
|
// Then check that the RHS is assignable to it.
|
|
if (node.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
checkForInOrForOfVariableDeclaration(node);
|
|
}
|
|
else {
|
|
const varExpr = node.initializer;
|
|
const iteratedType = checkRightHandSideOfForOf(node.expression, node.awaitModifier);
|
|
|
|
// There may be a destructuring assignment on the left side
|
|
if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
// iteratedType may be undefined. In this case, we still want to check the structure of
|
|
// varExpr, in particular making sure it's a valid LeftHandSideExpression. But we'd like
|
|
// to short circuit the type relation checking as much as possible, so we pass the unknownType.
|
|
checkDestructuringAssignment(varExpr, iteratedType || errorType);
|
|
}
|
|
else {
|
|
const leftType = checkExpression(varExpr);
|
|
checkReferenceExpression(
|
|
varExpr,
|
|
Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access,
|
|
Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access);
|
|
|
|
// iteratedType will be undefined if the rightType was missing properties/signatures
|
|
// required to get its iteratedType (like [Symbol.iterator] or next). This may be
|
|
// because we accessed properties from anyType, or it may have led to an error inside
|
|
// getElementTypeOfIterable.
|
|
if (iteratedType) {
|
|
checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression);
|
|
}
|
|
}
|
|
}
|
|
|
|
checkSourceElement(node.statement);
|
|
if (node.locals) {
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
}
|
|
|
|
function checkForInStatement(node: ForInStatement) {
|
|
// Grammar checking
|
|
checkGrammarForInOrForOfStatement(node);
|
|
|
|
const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression));
|
|
// TypeScript 1.0 spec (April 2014): 5.4
|
|
// In a 'for-in' statement of the form
|
|
// for (let VarDecl in Expr) Statement
|
|
// VarDecl must be a variable declaration without a type annotation that declares a variable of type Any,
|
|
// and Expr must be an expression of type Any, an object type, or a type parameter type.
|
|
if (node.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
const variable = (<VariableDeclarationList>node.initializer).declarations[0];
|
|
if (variable && isBindingPattern(variable.name)) {
|
|
error(variable.name, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern);
|
|
}
|
|
checkForInOrForOfVariableDeclaration(node);
|
|
}
|
|
else {
|
|
// In a 'for-in' statement of the form
|
|
// for (Var in Expr) Statement
|
|
// Var must be an expression classified as a reference of type Any or the String primitive type,
|
|
// and Expr must be an expression of type Any, an object type, or a type parameter type.
|
|
const varExpr = node.initializer;
|
|
const leftType = checkExpression(varExpr);
|
|
if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern);
|
|
}
|
|
else if (!isTypeAssignableTo(getIndexTypeOrString(rightType), leftType)) {
|
|
error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any);
|
|
}
|
|
else {
|
|
// run check only former check succeeded to avoid cascading errors
|
|
checkReferenceExpression(
|
|
varExpr,
|
|
Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access,
|
|
Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access);
|
|
}
|
|
}
|
|
|
|
// unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved
|
|
// in this case error about missing name is already reported - do not report extra one
|
|
if (rightType === neverType || !isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) {
|
|
error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter_but_here_has_type_0, typeToString(rightType));
|
|
}
|
|
|
|
checkSourceElement(node.statement);
|
|
if (node.locals) {
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
}
|
|
|
|
function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void {
|
|
const variableDeclarationList = <VariableDeclarationList>iterationStatement.initializer;
|
|
// checkGrammarForInOrForOfStatement will check that there is exactly one declaration.
|
|
if (variableDeclarationList.declarations.length >= 1) {
|
|
const decl = variableDeclarationList.declarations[0];
|
|
checkVariableDeclaration(decl);
|
|
}
|
|
}
|
|
|
|
function checkRightHandSideOfForOf(rhsExpression: Expression, awaitModifier: AwaitKeywordToken | undefined): Type {
|
|
const expressionType = checkNonNullExpression(rhsExpression);
|
|
const use = awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf;
|
|
return checkIteratedTypeOrElementType(use, expressionType, undefinedType, rhsExpression);
|
|
}
|
|
|
|
function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type {
|
|
if (isTypeAny(inputType)) {
|
|
return inputType;
|
|
}
|
|
|
|
return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType;
|
|
}
|
|
|
|
/**
|
|
* When consuming an iterable type in a for..of, spread, or iterator destructuring assignment
|
|
* we want to get the iterated type of an iterable for ES2015 or later, or the iterated type
|
|
* of a iterable (if defined globally) or element type of an array like for ES2015 or earlier.
|
|
*/
|
|
function getIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined, checkAssignability: boolean): Type | undefined {
|
|
const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0;
|
|
if (inputType === neverType) {
|
|
reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217
|
|
return undefined;
|
|
}
|
|
|
|
const uplevelIteration = languageVersion >= ScriptTarget.ES2015;
|
|
const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration;
|
|
|
|
// Get the iterated type of an `Iterable<T>` or `IterableIterator<T>` only in ES2015
|
|
// or higher, when inside of an async generator or for-await-if, or when
|
|
// downlevelIteration is requested.
|
|
if (uplevelIteration || downlevelIteration || allowAsyncIterables) {
|
|
// We only report errors for an invalid iterable type in ES2015 or higher.
|
|
const iterationTypes = getIterationTypesOfIterable(inputType, use, uplevelIteration ? errorNode : undefined);
|
|
if (checkAssignability) {
|
|
if (iterationTypes) {
|
|
const diagnostic =
|
|
use & IterationUse.ForOfFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_for_of_will_always_send_0 :
|
|
use & IterationUse.SpreadFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_spread_will_always_send_0 :
|
|
use & IterationUse.DestructuringFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_destructuring_will_always_send_0 :
|
|
use & IterationUse.YieldStarFlag ? Diagnostics.Cannot_delegate_iteration_to_value_because_the_next_method_of_its_iterator_expects_type_1_but_the_containing_generator_will_always_send_0 :
|
|
undefined;
|
|
if (diagnostic) {
|
|
checkTypeAssignableTo(sentType, iterationTypes.nextType, errorNode, diagnostic);
|
|
}
|
|
}
|
|
}
|
|
if (iterationTypes || uplevelIteration) {
|
|
return iterationTypes && iterationTypes.yieldType;
|
|
}
|
|
}
|
|
|
|
let arrayType = inputType;
|
|
let reportedError = false;
|
|
let hasStringConstituent = false;
|
|
|
|
// If strings are permitted, remove any string-like constituents from the array type.
|
|
// This allows us to find other non-string element types from an array unioned with
|
|
// a string.
|
|
if (use & IterationUse.AllowsStringInputFlag) {
|
|
if (arrayType.flags & TypeFlags.Union) {
|
|
// After we remove all types that are StringLike, we will know if there was a string constituent
|
|
// based on whether the result of filter is a new array.
|
|
const arrayTypes = (<UnionType>inputType).types;
|
|
const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike));
|
|
if (filteredTypes !== arrayTypes) {
|
|
arrayType = getUnionType(filteredTypes, UnionReduction.Subtype);
|
|
}
|
|
}
|
|
else if (arrayType.flags & TypeFlags.StringLike) {
|
|
arrayType = neverType;
|
|
}
|
|
|
|
hasStringConstituent = arrayType !== inputType;
|
|
if (hasStringConstituent) {
|
|
if (languageVersion < ScriptTarget.ES5) {
|
|
if (errorNode) {
|
|
error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher);
|
|
reportedError = true;
|
|
}
|
|
}
|
|
|
|
// Now that we've removed all the StringLike types, if no constituents remain, then the entire
|
|
// arrayOrStringType was a string.
|
|
if (arrayType.flags & TypeFlags.Never) {
|
|
return stringType;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isArrayLikeType(arrayType)) {
|
|
if (errorNode && !reportedError) {
|
|
// Which error we report depends on whether we allow strings or if there was a
|
|
// string constituent. For example, if the input type is number | string, we
|
|
// want to say that number is not an array type. But if the input was just
|
|
// number and string input is allowed, we want to say that number is not an
|
|
// array type or a string type.
|
|
const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined);
|
|
const [defaultDiagnostic, maybeMissingAwait]: [DiagnosticMessage, boolean] = !(use & IterationUse.AllowsStringInputFlag) || hasStringConstituent
|
|
? downlevelIteration
|
|
? [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true]
|
|
: yieldType
|
|
? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false]
|
|
: [Diagnostics.Type_0_is_not_an_array_type, true]
|
|
: downlevelIteration
|
|
? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true]
|
|
: yieldType
|
|
? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false]
|
|
: [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true];
|
|
errorAndMaybeSuggestAwait(
|
|
errorNode,
|
|
maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType),
|
|
defaultDiagnostic,
|
|
typeToString(arrayType));
|
|
}
|
|
return hasStringConstituent ? stringType : undefined;
|
|
}
|
|
|
|
const arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number);
|
|
if (hasStringConstituent && arrayElementType) {
|
|
// This is just an optimization for the case where arrayOrStringType is string | string[]
|
|
if (arrayElementType.flags & TypeFlags.StringLike) {
|
|
return stringType;
|
|
}
|
|
|
|
return getUnionType([arrayElementType, stringType], UnionReduction.Subtype);
|
|
}
|
|
|
|
return arrayElementType;
|
|
}
|
|
|
|
/**
|
|
* Gets the requested "iteration type" from an `Iterable`-like or `AsyncIterable`-like type.
|
|
*/
|
|
function getIterationTypeOfIterable(use: IterationUse, typeKind: IterationTypeKind, inputType: Type, errorNode: Node | undefined): Type | undefined {
|
|
if (isTypeAny(inputType)) {
|
|
return undefined;
|
|
}
|
|
|
|
const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode);
|
|
return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)];
|
|
}
|
|
|
|
function createIterationTypes(yieldType: Type = neverType, returnType: Type = neverType, nextType: Type = unknownType): IterationTypes {
|
|
// `yieldType` and `returnType` are defaulted to `neverType` they each will be combined
|
|
// via `getUnionType` when merging iteration types. `nextType` is defined as `unknownType`
|
|
// as it is combined via `getIntersectionType` when merging iteration types.
|
|
|
|
// Use the cache only for intrinsic types to keep it small as they are likely to be
|
|
// more frequently created (i.e. `Iterator<number, void, unknown>`). Iteration types
|
|
// are also cached on the type they are requested for, so we shouldn't need to maintain
|
|
// the cache for less-frequently used types.
|
|
if (yieldType.flags & TypeFlags.Intrinsic &&
|
|
returnType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined) &&
|
|
nextType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined)) {
|
|
const id = getTypeListId([yieldType, returnType, nextType]);
|
|
let iterationTypes = iterationTypesCache.get(id);
|
|
if (!iterationTypes) {
|
|
iterationTypes = { yieldType, returnType, nextType };
|
|
iterationTypesCache.set(id, iterationTypes);
|
|
}
|
|
return iterationTypes;
|
|
}
|
|
return { yieldType, returnType, nextType };
|
|
}
|
|
|
|
/**
|
|
* Combines multiple `IterationTypes` records.
|
|
*
|
|
* If `array` is empty or all elements are missing or are references to `noIterationTypes`,
|
|
* then `noIterationTypes` is returned. Otherwise, an `IterationTypes` record is returned
|
|
* for the combined iteration types.
|
|
*/
|
|
function combineIterationTypes(array: (IterationTypes | undefined)[]) {
|
|
let yieldTypes: Type[] | undefined;
|
|
let returnTypes: Type[] | undefined;
|
|
let nextTypes: Type[] | undefined;
|
|
for (const iterationTypes of array) {
|
|
if (iterationTypes === undefined || iterationTypes === noIterationTypes) {
|
|
continue;
|
|
}
|
|
if (iterationTypes === anyIterationTypes) {
|
|
return anyIterationTypes;
|
|
}
|
|
yieldTypes = append(yieldTypes, iterationTypes.yieldType);
|
|
returnTypes = append(returnTypes, iterationTypes.returnType);
|
|
nextTypes = append(nextTypes, iterationTypes.nextType);
|
|
}
|
|
if (yieldTypes || returnTypes || nextTypes) {
|
|
return createIterationTypes(
|
|
yieldTypes && getUnionType(yieldTypes),
|
|
returnTypes && getUnionType(returnTypes),
|
|
nextTypes && getIntersectionType(nextTypes));
|
|
}
|
|
return noIterationTypes;
|
|
}
|
|
|
|
function getCachedIterationTypes(type: Type, cacheKey: MatchingKeys<IterableOrIteratorType, IterationTypes | undefined>) {
|
|
return (type as IterableOrIteratorType)[cacheKey];
|
|
}
|
|
|
|
function setCachedIterationTypes(type: Type, cacheKey: MatchingKeys<IterableOrIteratorType, IterationTypes | undefined>, cachedTypes: IterationTypes) {
|
|
return (type as IterableOrIteratorType)[cacheKey] = cachedTypes;
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield*, *return*, and *next* types from an `Iterable`-like or `AsyncIterable`-like type.
|
|
*
|
|
* At every level that involves analyzing return types of signatures, we union the return types of all the signatures.
|
|
*
|
|
* Another thing to note is that at any step of this process, we could run into a dead end,
|
|
* meaning either the property is missing, or we run into the anyType. If either of these things
|
|
* happens, we return `undefined` to signal that we could not find the iteration type. If a property
|
|
* is missing, and the previous step did not result in `any`, then we also give an error if the
|
|
* caller requested it. Then the caller can decide what to do in the case where there is no iterated
|
|
* type.
|
|
*
|
|
* For a **for-of** statement, `yield*` (in a normal generator), spread, array
|
|
* destructuring, or normal generator we will only ever look for a `[Symbol.iterator]()`
|
|
* method.
|
|
*
|
|
* For an async generator we will only ever look at the `[Symbol.asyncIterator]()` method.
|
|
*
|
|
* For a **for-await-of** statement or a `yield*` in an async generator we will look for
|
|
* the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method.
|
|
*/
|
|
function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) {
|
|
if (isTypeAny(type)) {
|
|
return anyIterationTypes;
|
|
}
|
|
|
|
if (!(type.flags & TypeFlags.Union)) {
|
|
const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode);
|
|
if (iterationTypes === noIterationTypes) {
|
|
if (errorNode) {
|
|
reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag));
|
|
}
|
|
return undefined;
|
|
}
|
|
return iterationTypes;
|
|
}
|
|
|
|
const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable";
|
|
const cachedTypes = getCachedIterationTypes(type, cacheKey);
|
|
if (cachedTypes) return cachedTypes === noIterationTypes ? undefined : cachedTypes;
|
|
|
|
let allIterationTypes: IterationTypes[] | undefined;
|
|
for (const constituent of (type as UnionType).types) {
|
|
const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode);
|
|
if (iterationTypes === noIterationTypes) {
|
|
if (errorNode) {
|
|
reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag));
|
|
errorNode = undefined;
|
|
}
|
|
}
|
|
else {
|
|
allIterationTypes = append(allIterationTypes, iterationTypes);
|
|
}
|
|
}
|
|
|
|
const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes;
|
|
setCachedIterationTypes(type, cacheKey, iterationTypes);
|
|
return iterationTypes === noIterationTypes ? undefined : iterationTypes;
|
|
}
|
|
|
|
function getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) {
|
|
if (iterationTypes === noIterationTypes) return noIterationTypes;
|
|
if (iterationTypes === anyIterationTypes) return anyIterationTypes;
|
|
const { yieldType, returnType, nextType } = iterationTypes;
|
|
return createIterationTypes(
|
|
getAwaitedType(yieldType, errorNode) || anyType,
|
|
getAwaitedType(returnType, errorNode) || anyType,
|
|
nextType);
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield*, *return*, and *next* types from a non-union type.
|
|
*
|
|
* If we are unable to find the *yield*, *return*, and *next* types, `noIterationTypes` is
|
|
* returned to indicate to the caller that it should report an error. Otherwise, an
|
|
* `IterationTypes` record is returned.
|
|
*
|
|
* NOTE: You probably don't want to call this directly and should be calling
|
|
* `getIterationTypesOfIterable` instead.
|
|
*/
|
|
function getIterationTypesOfIterableWorker(type: Type, use: IterationUse, errorNode: Node | undefined) {
|
|
if (isTypeAny(type)) {
|
|
return anyIterationTypes;
|
|
}
|
|
|
|
if (use & IterationUse.AllowsAsyncIterablesFlag) {
|
|
const iterationTypes =
|
|
getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) ||
|
|
getIterationTypesOfIterableFast(type, asyncIterationTypesResolver);
|
|
if (iterationTypes) {
|
|
return iterationTypes;
|
|
}
|
|
}
|
|
|
|
if (use & IterationUse.AllowsSyncIterablesFlag) {
|
|
const iterationTypes =
|
|
getIterationTypesOfIterableCached(type, syncIterationTypesResolver) ||
|
|
getIterationTypesOfIterableFast(type, syncIterationTypesResolver);
|
|
if (iterationTypes) {
|
|
if (use & IterationUse.AllowsAsyncIterablesFlag) {
|
|
// for a sync iterable in an async context, only use the cached types if they are valid.
|
|
if (iterationTypes !== noIterationTypes) {
|
|
return setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", getAsyncFromSyncIterationTypes(iterationTypes, errorNode));
|
|
}
|
|
}
|
|
else {
|
|
return iterationTypes;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (use & IterationUse.AllowsAsyncIterablesFlag) {
|
|
const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode);
|
|
if (iterationTypes !== noIterationTypes) {
|
|
return iterationTypes;
|
|
}
|
|
}
|
|
|
|
if (use & IterationUse.AllowsSyncIterablesFlag) {
|
|
const iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode);
|
|
if (iterationTypes !== noIterationTypes) {
|
|
if (use & IterationUse.AllowsAsyncIterablesFlag) {
|
|
return setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes
|
|
? getAsyncFromSyncIterationTypes(iterationTypes, errorNode)
|
|
: noIterationTypes);
|
|
}
|
|
else {
|
|
return iterationTypes;
|
|
}
|
|
}
|
|
}
|
|
|
|
return noIterationTypes;
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield*, *return*, and *next* types of an `Iterable`-like or
|
|
* `AsyncIterable`-like type from the cache.
|
|
*
|
|
* NOTE: You probably don't want to call this directly and should be calling
|
|
* `getIterationTypesOfIterable` instead.
|
|
*/
|
|
function getIterationTypesOfIterableCached(type: Type, resolver: IterationTypesResolver) {
|
|
return getCachedIterationTypes(type, resolver.iterableCacheKey);
|
|
}
|
|
|
|
function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) {
|
|
const globalIterationTypes =
|
|
getIterationTypesOfIterableCached(globalType, resolver) ||
|
|
getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined);
|
|
return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes;
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like
|
|
* type from from common heuristics.
|
|
*
|
|
* If we previously analyzed this type and found no iteration types, `noIterationTypes` is
|
|
* returned. If we found iteration types, an `IterationTypes` record is returned.
|
|
* Otherwise, we return `undefined` to indicate to the caller it should perform a more
|
|
* exhaustive analysis.
|
|
*
|
|
* NOTE: You probably don't want to call this directly and should be calling
|
|
* `getIterationTypesOfIterable` instead.
|
|
*/
|
|
function getIterationTypesOfIterableFast(type: Type, resolver: IterationTypesResolver) {
|
|
// As an optimization, if the type is an instantiation of one of the following global types, then
|
|
// just grab its related type argument:
|
|
// - `Iterable<T>` or `AsyncIterable<T>`
|
|
// - `IterableIterator<T>` or `AsyncIterableIterator<T>`
|
|
let globalType: Type;
|
|
if (isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) ||
|
|
isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))) {
|
|
const [yieldType] = getTypeArguments(type as GenericType);
|
|
// The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the
|
|
// iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins.
|
|
// While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use
|
|
// different definitions.
|
|
const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver);
|
|
return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(yieldType, returnType, nextType));
|
|
}
|
|
|
|
// As an optimization, if the type is an instantiation of the following global type, then
|
|
// just grab its related type arguments:
|
|
// - `Generator<T, TReturn, TNext>` or `AsyncGenerator<T, TReturn, TNext>`
|
|
if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) {
|
|
const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType);
|
|
return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(yieldType, returnType, nextType));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like
|
|
* type from its members.
|
|
*
|
|
* If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes`
|
|
* record is returned. Otherwise, `noIterationTypes` is returned.
|
|
*
|
|
* NOTE: You probably don't want to call this directly and should be calling
|
|
* `getIterationTypesOfIterable` instead.
|
|
*/
|
|
function getIterationTypesOfIterableSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) {
|
|
const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName));
|
|
const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined;
|
|
if (isTypeAny(methodType)) {
|
|
return setCachedIterationTypes(type, resolver.iterableCacheKey, anyIterationTypes);
|
|
}
|
|
|
|
const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined;
|
|
if (!some(signatures)) {
|
|
return setCachedIterationTypes(type, resolver.iterableCacheKey, noIterationTypes);
|
|
}
|
|
|
|
const iteratorType = getUnionType(map(signatures, getReturnTypeOfSignature), UnionReduction.Subtype);
|
|
const iterationTypes = getIterationTypesOfIterator(iteratorType, resolver, errorNode) ?? noIterationTypes;
|
|
return setCachedIterationTypes(type, resolver.iterableCacheKey, iterationTypes);
|
|
}
|
|
|
|
function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): void {
|
|
const message = allowAsyncIterables
|
|
? Diagnostics.Type_0_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator
|
|
: Diagnostics.Type_0_must_have_a_Symbol_iterator_method_that_returns_an_iterator;
|
|
errorAndMaybeSuggestAwait(errorNode, !!getAwaitedTypeOfPromise(type), message, typeToString(type));
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type.
|
|
*
|
|
* If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes`
|
|
* record is returned. Otherwise, `undefined` is returned.
|
|
*/
|
|
function getIterationTypesOfIterator(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) {
|
|
if (isTypeAny(type)) {
|
|
return anyIterationTypes;
|
|
}
|
|
|
|
const iterationTypes =
|
|
getIterationTypesOfIteratorCached(type, resolver) ||
|
|
getIterationTypesOfIteratorFast(type, resolver) ||
|
|
getIterationTypesOfIteratorSlow(type, resolver, errorNode);
|
|
return iterationTypes === noIterationTypes ? undefined : iterationTypes;
|
|
}
|
|
|
|
/**
|
|
* Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the
|
|
* cache.
|
|
*
|
|
* NOTE: You probably don't want to call this directly and should be calling
|
|
* `getIterationTypesOfIterator` instead.
|
|
*/
|
|
function getIterationTypesOfIteratorCached(type: Type, resolver: IterationTypesResolver) {
|
|
return getCachedIterationTypes(type, resolver.iteratorCacheKey);
|
|
}
|
|
|
|
/**
|
|
* Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the
|
|
* cache or from common heuristics.
|
|
*
|
|
* If we previously analyzed this type and found no iteration types, `noIterationTypes` is
|
|
* returned. If we found iteration types, an `IterationTypes` record is returned.
|
|
* Otherwise, we return `undefined` to indicate to the caller it should perform a more
|
|
* exhaustive analysis.
|
|
*
|
|
* NOTE: You probably don't want to call this directly and should be calling
|
|
* `getIterationTypesOfIterator` instead.
|
|
*/
|
|
function getIterationTypesOfIteratorFast(type: Type, resolver: IterationTypesResolver) {
|
|
// As an optimization, if the type is an instantiation of one of the following global types,
|
|
// then just grab its related type argument:
|
|
// - `IterableIterator<T>` or `AsyncIterableIterator<T>`
|
|
// - `Iterator<T, TReturn, TNext>` or `AsyncIterator<T, TReturn, TNext>`
|
|
// - `Generator<T, TReturn, TNext>` or `AsyncGenerator<T, TReturn, TNext>`
|
|
const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false);
|
|
if (isReferenceToType(type, globalType)) {
|
|
const [yieldType] = getTypeArguments(type as GenericType);
|
|
// The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the
|
|
// iteration types of their `next`, `return`, and `throw` methods. While we define these as `any`
|
|
// and `undefined` in our libs by default, a custom lib *could* use different definitions.
|
|
const globalIterationTypes =
|
|
getIterationTypesOfIteratorCached(globalType, resolver) ||
|
|
getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined);
|
|
const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes;
|
|
return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType));
|
|
}
|
|
if (isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) ||
|
|
isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) {
|
|
const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType);
|
|
return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType));
|
|
}
|
|
}
|
|
|
|
function isIteratorResult(type: Type, kind: IterationTypeKind.Yield | IterationTypeKind.Return) {
|
|
// From https://tc39.github.io/ecma262/#sec-iteratorresult-interface:
|
|
// > [done] is the result status of an iterator `next` method call. If the end of the iterator was reached `done` is `true`.
|
|
// > If the end was not reached `done` is `false` and a value is available.
|
|
// > If a `done` property (either own or inherited) does not exist, it is consider to have the value `false`.
|
|
const doneType = getTypeOfPropertyOfType(type, "done" as __String) || falseType;
|
|
return isTypeAssignableTo(kind === IterationTypeKind.Yield ? falseType : trueType, doneType);
|
|
}
|
|
|
|
function isYieldIteratorResult(type: Type) {
|
|
return isIteratorResult(type, IterationTypeKind.Yield);
|
|
}
|
|
|
|
function isReturnIteratorResult(type: Type) {
|
|
return isIteratorResult(type, IterationTypeKind.Return);
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield* and *return* types of an `IteratorResult`-like type.
|
|
*
|
|
* If we are unable to determine a *yield* or a *return* type, `noIterationTypes` is
|
|
* returned to indicate to the caller that it should handle the error. Otherwise, an
|
|
* `IterationTypes` record is returned.
|
|
*/
|
|
function getIterationTypesOfIteratorResult(type: Type) {
|
|
if (isTypeAny(type)) {
|
|
return anyIterationTypes;
|
|
}
|
|
|
|
const cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult");
|
|
if (cachedTypes) {
|
|
return cachedTypes;
|
|
}
|
|
|
|
// As an optimization, if the type is an instantiation of one of the global `IteratorYieldResult<T>`
|
|
// or `IteratorReturnResult<TReturn>` types, then just grab its type argument.
|
|
if (isReferenceToType(type, getGlobalIteratorYieldResultType(/*reportErrors*/ false))) {
|
|
const yieldType = getTypeArguments(type as GenericType)[0];
|
|
return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, /*returnType*/ undefined, /*nextType*/ undefined));
|
|
}
|
|
if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) {
|
|
const returnType = getTypeArguments(type as GenericType)[0];
|
|
return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined));
|
|
}
|
|
|
|
// Choose any constituents that can produce the requested iteration type.
|
|
const yieldIteratorResult = filterType(type, isYieldIteratorResult);
|
|
const yieldType = yieldIteratorResult !== neverType ? getTypeOfPropertyOfType(yieldIteratorResult, "value" as __String) : undefined;
|
|
|
|
const returnIteratorResult = filterType(type, isReturnIteratorResult);
|
|
const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined;
|
|
|
|
if (!yieldType && !returnType) {
|
|
return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes);
|
|
}
|
|
|
|
// From https://tc39.github.io/ecma262/#sec-iteratorresult-interface
|
|
// > ... If the iterator does not have a return value, `value` is `undefined`. In that case, the
|
|
// > `value` property may be absent from the conforming object if it does not inherit an explicit
|
|
// > `value` property.
|
|
return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, returnType || voidType, /*nextType*/ undefined));
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield*, *return*, and *next* types of a the `next()`, `return()`, or
|
|
* `throw()` method of an `Iterator`-like or `AsyncIterator`-like type.
|
|
*
|
|
* If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes`
|
|
* record is returned. Otherwise, we return `undefined`.
|
|
*/
|
|
function getIterationTypesOfMethod(type: Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined): IterationTypes | undefined {
|
|
const method = getPropertyOfType(type, methodName as __String);
|
|
|
|
// Ignore 'return' or 'throw' if they are missing.
|
|
if (!method && methodName !== "next") {
|
|
return undefined;
|
|
}
|
|
|
|
const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional))
|
|
? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull)
|
|
: undefined;
|
|
|
|
if (isTypeAny(methodType)) {
|
|
// `return()` and `throw()` don't provide a *next* type.
|
|
return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext;
|
|
}
|
|
|
|
// Both async and non-async iterators *must* have a `next` method.
|
|
const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray;
|
|
if (methodSignatures.length === 0) {
|
|
if (errorNode) {
|
|
const diagnostic = methodName === "next"
|
|
? resolver.mustHaveANextMethodDiagnostic
|
|
: resolver.mustBeAMethodDiagnostic;
|
|
error(errorNode, diagnostic, methodName);
|
|
}
|
|
return methodName === "next" ? anyIterationTypes : undefined;
|
|
}
|
|
|
|
// Extract the first parameter and return type of each signature.
|
|
let methodParameterTypes: Type[] | undefined;
|
|
let methodReturnTypes: Type[] | undefined;
|
|
for (const signature of methodSignatures) {
|
|
if (methodName !== "throw" && some(signature.parameters)) {
|
|
methodParameterTypes = append(methodParameterTypes, getTypeAtPosition(signature, 0));
|
|
}
|
|
methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature));
|
|
}
|
|
|
|
// Resolve the *next* or *return* type from the first parameter of a `next()` or
|
|
// `return()` method, respectively.
|
|
let returnTypes: Type[] | undefined;
|
|
let nextType: Type | undefined;
|
|
if (methodName !== "throw") {
|
|
const methodParameterType = methodParameterTypes ? getUnionType(methodParameterTypes) : unknownType;
|
|
if (methodName === "next") {
|
|
// The value of `next(value)` is *not* awaited by async generators
|
|
nextType = methodParameterType;
|
|
}
|
|
else if (methodName === "return") {
|
|
// The value of `return(value)` *is* awaited by async generators
|
|
const resolvedMethodParameterType = resolver.resolveIterationType(methodParameterType, errorNode) || anyType;
|
|
returnTypes = append(returnTypes, resolvedMethodParameterType);
|
|
}
|
|
}
|
|
|
|
// Resolve the *yield* and *return* types from the return type of the method (i.e. `IteratorResult`)
|
|
let yieldType: Type;
|
|
const methodReturnType = methodReturnTypes ? getUnionType(methodReturnTypes, UnionReduction.Subtype) : neverType;
|
|
const resolvedMethodReturnType = resolver.resolveIterationType(methodReturnType, errorNode) || anyType;
|
|
const iterationTypes = getIterationTypesOfIteratorResult(resolvedMethodReturnType);
|
|
if (iterationTypes === noIterationTypes) {
|
|
if (errorNode) {
|
|
error(errorNode, resolver.mustHaveAValueDiagnostic, methodName);
|
|
}
|
|
yieldType = anyType;
|
|
returnTypes = append(returnTypes, anyType);
|
|
}
|
|
else {
|
|
yieldType = iterationTypes.yieldType;
|
|
returnTypes = append(returnTypes, iterationTypes.returnType);
|
|
}
|
|
|
|
return createIterationTypes(yieldType, getUnionType(returnTypes), nextType);
|
|
}
|
|
|
|
/**
|
|
* Gets the *yield*, *return*, and *next* types of an `Iterator`-like or `AsyncIterator`-like
|
|
* type from its members.
|
|
*
|
|
* If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes`
|
|
* record is returned. Otherwise, `noIterationTypes` is returned.
|
|
*
|
|
* NOTE: You probably don't want to call this directly and should be calling
|
|
* `getIterationTypesOfIterator` instead.
|
|
*/
|
|
function getIterationTypesOfIteratorSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) {
|
|
const iterationTypes = combineIterationTypes([
|
|
getIterationTypesOfMethod(type, resolver, "next", errorNode),
|
|
getIterationTypesOfMethod(type, resolver, "return", errorNode),
|
|
getIterationTypesOfMethod(type, resolver, "throw", errorNode),
|
|
]);
|
|
return setCachedIterationTypes(type, resolver.iteratorCacheKey, iterationTypes);
|
|
}
|
|
|
|
/**
|
|
* Gets the requested "iteration type" from a type that is either `Iterable`-like, `Iterator`-like,
|
|
* `IterableIterator`-like, or `Generator`-like (for a non-async generator); or `AsyncIterable`-like,
|
|
* `AsyncIterator`-like, `AsyncIterableIterator`-like, or `AsyncGenerator`-like (for an async generator).
|
|
*/
|
|
function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: Type, isAsyncGenerator: boolean): Type | undefined {
|
|
if (isTypeAny(returnType)) {
|
|
return undefined;
|
|
}
|
|
|
|
const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator);
|
|
return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)];
|
|
}
|
|
|
|
function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) {
|
|
if (isTypeAny(type)) {
|
|
return anyIterationTypes;
|
|
}
|
|
|
|
const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType;
|
|
const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver;
|
|
return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) ||
|
|
getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined);
|
|
}
|
|
|
|
function checkBreakOrContinueStatement(node: BreakOrContinueStatement) {
|
|
// Grammar checking
|
|
if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node);
|
|
|
|
// TODO: Check that target label is valid
|
|
}
|
|
|
|
function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) {
|
|
const isGenerator = !!(functionFlags & FunctionFlags.Generator);
|
|
const isAsync = !!(functionFlags & FunctionFlags.Async);
|
|
return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) || errorType :
|
|
isAsync ? getPromisedTypeOfPromise(returnType) || errorType :
|
|
returnType;
|
|
}
|
|
|
|
function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean {
|
|
const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func));
|
|
return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown);
|
|
}
|
|
|
|
function checkReturnStatement(node: ReturnStatement) {
|
|
// Grammar checking
|
|
if (checkGrammarStatementInAmbientContext(node)) {
|
|
return;
|
|
}
|
|
|
|
const func = getContainingFunction(node);
|
|
if (!func) {
|
|
grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body);
|
|
return;
|
|
}
|
|
|
|
const signature = getSignatureFromDeclaration(func);
|
|
const returnType = getReturnTypeOfSignature(signature);
|
|
const functionFlags = getFunctionFlags(func);
|
|
if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) {
|
|
const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType;
|
|
if (func.kind === SyntaxKind.SetAccessor) {
|
|
if (node.expression) {
|
|
error(node, Diagnostics.Setters_cannot_return_a_value);
|
|
}
|
|
}
|
|
else if (func.kind === SyntaxKind.Constructor) {
|
|
if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) {
|
|
error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class);
|
|
}
|
|
}
|
|
else if (getReturnTypeFromAnnotation(func)) {
|
|
const unwrappedReturnType = unwrapReturnType(returnType, functionFlags);
|
|
const unwrappedExprType = functionFlags & FunctionFlags.Async
|
|
? checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)
|
|
: exprType;
|
|
if (unwrappedReturnType) {
|
|
// If the function has a return type, but promisedType is
|
|
// undefined, an error will be reported in checkAsyncFunctionReturnType
|
|
// so we don't need to report one here.
|
|
checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression);
|
|
}
|
|
}
|
|
}
|
|
else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) {
|
|
// The function has a return type, but the return statement doesn't have an expression.
|
|
error(node, Diagnostics.Not_all_code_paths_return_a_value);
|
|
}
|
|
}
|
|
|
|
function checkWithStatement(node: WithStatement) {
|
|
// Grammar checking for withStatement
|
|
if (!checkGrammarStatementInAmbientContext(node)) {
|
|
if (node.flags & NodeFlags.AwaitContext) {
|
|
grammarErrorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_an_async_function_block);
|
|
}
|
|
}
|
|
|
|
checkExpression(node.expression);
|
|
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
if (!hasParseDiagnostics(sourceFile)) {
|
|
const start = getSpanOfTokenAtPosition(sourceFile, node.pos).start;
|
|
const end = node.statement.pos;
|
|
grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.The_with_statement_is_not_supported_All_symbols_in_a_with_block_will_have_type_any);
|
|
}
|
|
}
|
|
|
|
function checkSwitchStatement(node: SwitchStatement) {
|
|
// Grammar checking
|
|
checkGrammarStatementInAmbientContext(node);
|
|
|
|
let firstDefaultClause: CaseOrDefaultClause;
|
|
let hasDuplicateDefaultClause = false;
|
|
|
|
const expressionType = checkExpression(node.expression);
|
|
const expressionIsLiteral = isLiteralType(expressionType);
|
|
forEach(node.caseBlock.clauses, clause => {
|
|
// Grammar check for duplicate default clauses, skip if we already report duplicate default clause
|
|
if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) {
|
|
if (firstDefaultClause === undefined) {
|
|
firstDefaultClause = clause;
|
|
}
|
|
else {
|
|
grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement);
|
|
hasDuplicateDefaultClause = true;
|
|
}
|
|
}
|
|
|
|
if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) {
|
|
// TypeScript 1.0 spec (April 2014): 5.9
|
|
// In a 'switch' statement, each 'case' expression must be of a type that is comparable
|
|
// to or from the type of the 'switch' expression.
|
|
let caseType = checkExpression(clause.expression);
|
|
const caseIsLiteral = isLiteralType(caseType);
|
|
let comparedExpressionType = expressionType;
|
|
if (!caseIsLiteral || !expressionIsLiteral) {
|
|
caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType;
|
|
comparedExpressionType = getBaseTypeOfLiteralType(expressionType);
|
|
}
|
|
if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) {
|
|
// expressionType is not comparable to caseType, try the reversed check and report errors if it fails
|
|
checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined);
|
|
}
|
|
}
|
|
forEach(clause.statements, checkSourceElement);
|
|
if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) {
|
|
error(clause, Diagnostics.Fallthrough_case_in_switch);
|
|
}
|
|
});
|
|
if (node.caseBlock.locals) {
|
|
registerForUnusedIdentifiersCheck(node.caseBlock);
|
|
}
|
|
}
|
|
|
|
function checkLabeledStatement(node: LabeledStatement) {
|
|
// Grammar checking
|
|
if (!checkGrammarStatementInAmbientContext(node)) {
|
|
findAncestor(node.parent, current => {
|
|
if (isFunctionLike(current)) {
|
|
return "quit";
|
|
}
|
|
if (current.kind === SyntaxKind.LabeledStatement && (<LabeledStatement>current).label.escapedText === node.label.escapedText) {
|
|
grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label));
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
// ensure that label is unique
|
|
checkSourceElement(node.statement);
|
|
}
|
|
|
|
function checkThrowStatement(node: ThrowStatement) {
|
|
// Grammar checking
|
|
if (!checkGrammarStatementInAmbientContext(node)) {
|
|
if (node.expression === undefined) {
|
|
grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here);
|
|
}
|
|
}
|
|
|
|
if (node.expression) {
|
|
checkExpression(node.expression);
|
|
}
|
|
}
|
|
|
|
function checkTryStatement(node: TryStatement) {
|
|
// Grammar checking
|
|
checkGrammarStatementInAmbientContext(node);
|
|
|
|
checkBlock(node.tryBlock);
|
|
const catchClause = node.catchClause;
|
|
if (catchClause) {
|
|
// Grammar checking
|
|
if (catchClause.variableDeclaration) {
|
|
if (catchClause.variableDeclaration.type) {
|
|
grammarErrorOnFirstToken(catchClause.variableDeclaration.type, Diagnostics.Catch_clause_variable_cannot_have_a_type_annotation);
|
|
}
|
|
else if (catchClause.variableDeclaration.initializer) {
|
|
grammarErrorOnFirstToken(catchClause.variableDeclaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer);
|
|
}
|
|
else {
|
|
const blockLocals = catchClause.block.locals;
|
|
if (blockLocals) {
|
|
forEachKey(catchClause.locals!, caughtName => {
|
|
const blockLocal = blockLocals.get(caughtName);
|
|
if (blockLocal && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) {
|
|
grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
checkBlock(catchClause.block);
|
|
}
|
|
|
|
if (node.finallyBlock) {
|
|
checkBlock(node.finallyBlock);
|
|
}
|
|
}
|
|
|
|
function checkIndexConstraints(type: Type) {
|
|
const declaredNumberIndexer = getIndexDeclarationOfSymbol(type.symbol, IndexKind.Number);
|
|
const declaredStringIndexer = getIndexDeclarationOfSymbol(type.symbol, IndexKind.String);
|
|
|
|
const stringIndexType = getIndexTypeOfType(type, IndexKind.String);
|
|
const numberIndexType = getIndexTypeOfType(type, IndexKind.Number);
|
|
|
|
if (stringIndexType || numberIndexType) {
|
|
forEach(getPropertiesOfObjectType(type), prop => {
|
|
const propType = getTypeOfSymbol(prop);
|
|
checkIndexConstraintForProperty(prop, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String);
|
|
checkIndexConstraintForProperty(prop, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number);
|
|
});
|
|
|
|
const classDeclaration = type.symbol.valueDeclaration;
|
|
if (getObjectFlags(type) & ObjectFlags.Class && isClassLike(classDeclaration)) {
|
|
for (const member of classDeclaration.members) {
|
|
// Only process instance properties with computed names here.
|
|
// Static properties cannot be in conflict with indexers,
|
|
// and properties with literal names were already checked.
|
|
if (!hasModifier(member, ModifierFlags.Static) && hasNonBindableDynamicName(member)) {
|
|
const symbol = getSymbolOfNode(member);
|
|
const propType = getTypeOfSymbol(symbol);
|
|
checkIndexConstraintForProperty(symbol, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String);
|
|
checkIndexConstraintForProperty(symbol, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let errorNode: Node | undefined;
|
|
if (stringIndexType && numberIndexType) {
|
|
errorNode = declaredNumberIndexer || declaredStringIndexer;
|
|
// condition 'errorNode === undefined' may appear if types does not declare nor string neither number indexer
|
|
if (!errorNode && (getObjectFlags(type) & ObjectFlags.Interface)) {
|
|
const someBaseTypeHasBothIndexers = forEach(getBaseTypes(<InterfaceType>type), base => getIndexTypeOfType(base, IndexKind.String) && getIndexTypeOfType(base, IndexKind.Number));
|
|
errorNode = someBaseTypeHasBothIndexers ? undefined : type.symbol.declarations[0];
|
|
}
|
|
}
|
|
|
|
if (errorNode && !isTypeAssignableTo(numberIndexType!, stringIndexType!)) { // TODO: GH#18217
|
|
error(errorNode, Diagnostics.Numeric_index_type_0_is_not_assignable_to_string_index_type_1,
|
|
typeToString(numberIndexType!), typeToString(stringIndexType!));
|
|
}
|
|
|
|
function checkIndexConstraintForProperty(
|
|
prop: Symbol,
|
|
propertyType: Type,
|
|
containingType: Type,
|
|
indexDeclaration: Declaration | undefined,
|
|
indexType: Type | undefined,
|
|
indexKind: IndexKind): void {
|
|
|
|
// ESSymbol properties apply to neither string nor numeric indexers.
|
|
if (!indexType || isKnownSymbol(prop)) {
|
|
return;
|
|
}
|
|
|
|
const propDeclaration = prop.valueDeclaration;
|
|
const name = propDeclaration && getNameOfDeclaration(propDeclaration);
|
|
|
|
// index is numeric and property name is not valid numeric literal
|
|
if (indexKind === IndexKind.Number && !(name ? isNumericName(name) : isNumericLiteralName(prop.escapedName))) {
|
|
return;
|
|
}
|
|
|
|
// perform property check if property or indexer is declared in 'type'
|
|
// this allows us to rule out cases when both property and indexer are inherited from the base class
|
|
let errorNode: Node | undefined;
|
|
if (propDeclaration && name &&
|
|
(propDeclaration.kind === SyntaxKind.BinaryExpression ||
|
|
name.kind === SyntaxKind.ComputedPropertyName ||
|
|
prop.parent === containingType.symbol)) {
|
|
errorNode = propDeclaration;
|
|
}
|
|
else if (indexDeclaration) {
|
|
errorNode = indexDeclaration;
|
|
}
|
|
else if (getObjectFlags(containingType) & ObjectFlags.Interface) {
|
|
// for interfaces property and indexer might be inherited from different bases
|
|
// check if any base class already has both property and indexer.
|
|
// check should be performed only if 'type' is the first type that brings property\indexer together
|
|
const someBaseClassHasBothPropertyAndIndexer = forEach(getBaseTypes(<InterfaceType>containingType), base => getPropertyOfObjectType(base, prop.escapedName) && getIndexTypeOfType(base, indexKind));
|
|
errorNode = someBaseClassHasBothPropertyAndIndexer ? undefined : containingType.symbol.declarations[0];
|
|
}
|
|
|
|
if (errorNode && !isTypeAssignableTo(propertyType, indexType)) {
|
|
const errorMessage =
|
|
indexKind === IndexKind.String
|
|
? Diagnostics.Property_0_of_type_1_is_not_assignable_to_string_index_type_2
|
|
: Diagnostics.Property_0_of_type_1_is_not_assignable_to_numeric_index_type_2;
|
|
error(errorNode, errorMessage, symbolToString(prop), typeToString(propertyType), typeToString(indexType));
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkTypeNameIsReserved(name: Identifier, message: DiagnosticMessage): void {
|
|
// TS 1.0 spec (April 2014): 3.6.1
|
|
// The predefined type keywords are reserved and cannot be used as names of user defined types.
|
|
switch (name.escapedText) {
|
|
case "any":
|
|
case "unknown":
|
|
case "number":
|
|
case "bigint":
|
|
case "boolean":
|
|
case "string":
|
|
case "symbol":
|
|
case "void":
|
|
case "object":
|
|
error(name, message, name.escapedText as string);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The name cannot be used as 'Object' of user defined types with special target.
|
|
*/
|
|
function checkClassNameCollisionWithObject(name: Identifier): void {
|
|
if (languageVersion === ScriptTarget.ES5 && name.escapedText === "Object"
|
|
&& moduleKind < ModuleKind.ES2015) {
|
|
error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check each type parameter and check that type parameters have no duplicate type parameter declarations
|
|
*/
|
|
function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) {
|
|
if (typeParameterDeclarations) {
|
|
let seenDefault = false;
|
|
for (let i = 0; i < typeParameterDeclarations.length; i++) {
|
|
const node = typeParameterDeclarations[i];
|
|
checkTypeParameter(node);
|
|
|
|
if (produceDiagnostics) {
|
|
if (node.default) {
|
|
seenDefault = true;
|
|
checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i);
|
|
}
|
|
else if (seenDefault) {
|
|
error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters);
|
|
}
|
|
for (let j = 0; j < i; j++) {
|
|
if (typeParameterDeclarations[j].symbol === node.symbol) {
|
|
error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Check that type parameter defaults only reference previously declared type parameters */
|
|
function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: readonly TypeParameterDeclaration[], index: number) {
|
|
visit(root);
|
|
function visit(node: Node) {
|
|
if (node.kind === SyntaxKind.TypeReference) {
|
|
const type = getTypeFromTypeReference(<TypeReferenceNode>node);
|
|
if (type.flags & TypeFlags.TypeParameter) {
|
|
for (let i = index; i < typeParameters.length; i++) {
|
|
if (type.symbol === getSymbolOfNode(typeParameters[i])) {
|
|
error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
forEachChild(node, visit);
|
|
}
|
|
}
|
|
|
|
/** Check that type parameter lists are identical across multiple declarations */
|
|
function checkTypeParameterListsIdentical(symbol: Symbol) {
|
|
if (symbol.declarations.length === 1) {
|
|
return;
|
|
}
|
|
|
|
const links = getSymbolLinks(symbol);
|
|
if (!links.typeParametersChecked) {
|
|
links.typeParametersChecked = true;
|
|
const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol);
|
|
if (declarations.length <= 1) {
|
|
return;
|
|
}
|
|
|
|
const type = <InterfaceType>getDeclaredTypeOfSymbol(symbol);
|
|
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) {
|
|
// Report an error on every conflicting declaration.
|
|
const name = symbolToString(symbol);
|
|
for (const declaration of declarations) {
|
|
error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function areTypeParametersIdentical(declarations: readonly (ClassDeclaration | InterfaceDeclaration)[], targetParameters: TypeParameter[]) {
|
|
const maxTypeArgumentCount = length(targetParameters);
|
|
const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters);
|
|
|
|
for (const declaration of declarations) {
|
|
// If this declaration has too few or too many type parameters, we report an error
|
|
const sourceParameters = getEffectiveTypeParameterDeclarations(declaration);
|
|
const numTypeParameters = sourceParameters.length;
|
|
if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) {
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < numTypeParameters; i++) {
|
|
const source = sourceParameters[i];
|
|
const target = targetParameters[i];
|
|
|
|
// If the type parameter node does not have the same as the resolved type
|
|
// parameter at this position, we report an error.
|
|
if (source.name.escapedText !== target.symbol.escapedName) {
|
|
return false;
|
|
}
|
|
|
|
// If the type parameter node does not have an identical constraint as the resolved
|
|
// type parameter at this position, we report an error.
|
|
const constraint = getEffectiveConstraintOfTypeParameter(source);
|
|
const sourceConstraint = constraint && getTypeFromTypeNode(constraint);
|
|
const targetConstraint = getConstraintOfTypeParameter(target);
|
|
// relax check if later interface augmentation has no constraint, it's more broad and is OK to merge with
|
|
// a more constrained interface (this could be generalized to a full hierarchy check, but that's maybe overkill)
|
|
if (sourceConstraint && targetConstraint && !isTypeIdenticalTo(sourceConstraint, targetConstraint)) {
|
|
return false;
|
|
}
|
|
|
|
// If the type parameter node has a default and it is not identical to the default
|
|
// for the type parameter at this position, we report an error.
|
|
const sourceDefault = source.default && getTypeFromTypeNode(source.default);
|
|
const targetDefault = getDefaultFromTypeParameter(target);
|
|
if (sourceDefault && targetDefault && !isTypeIdenticalTo(sourceDefault, targetDefault)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function checkClassExpression(node: ClassExpression): Type {
|
|
checkClassLikeDeclaration(node);
|
|
checkNodeDeferred(node);
|
|
return getTypeOfSymbol(getSymbolOfNode(node));
|
|
}
|
|
|
|
function checkClassExpressionDeferred(node: ClassExpression) {
|
|
forEach(node.members, checkSourceElement);
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
|
|
function checkClassDeclaration(node: ClassDeclaration) {
|
|
if (!node.name && !hasModifier(node, ModifierFlags.Default)) {
|
|
grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name);
|
|
}
|
|
checkClassLikeDeclaration(node);
|
|
forEach(node.members, checkSourceElement);
|
|
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
|
|
function checkClassLikeDeclaration(node: ClassLikeDeclaration) {
|
|
checkGrammarClassLikeDeclaration(node);
|
|
checkDecorators(node);
|
|
if (node.name) {
|
|
checkTypeNameIsReserved(node.name, Diagnostics.Class_name_cannot_be_0);
|
|
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
|
|
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
|
|
if (!(node.flags & NodeFlags.Ambient)) {
|
|
checkClassNameCollisionWithObject(node.name);
|
|
}
|
|
}
|
|
checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
|
|
checkExportsOnMergedDeclarations(node);
|
|
const symbol = getSymbolOfNode(node);
|
|
const type = <InterfaceType>getDeclaredTypeOfSymbol(symbol);
|
|
const typeWithThis = getTypeWithThisArgument(type);
|
|
const staticType = <ObjectType>getTypeOfSymbol(symbol);
|
|
checkTypeParameterListsIdentical(symbol);
|
|
checkClassForDuplicateDeclarations(node);
|
|
|
|
// Only check for reserved static identifiers on non-ambient context.
|
|
if (!(node.flags & NodeFlags.Ambient)) {
|
|
checkClassForStaticPropertyNameConflicts(node);
|
|
}
|
|
|
|
const baseTypeNode = getEffectiveBaseTypeNode(node);
|
|
if (baseTypeNode) {
|
|
forEach(baseTypeNode.typeArguments, checkSourceElement);
|
|
if (languageVersion < ScriptTarget.ES2015) {
|
|
checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends);
|
|
}
|
|
// check both @extends and extends if both are specified.
|
|
const extendsNode = getClassExtendsHeritageElement(node);
|
|
if (extendsNode && extendsNode !== baseTypeNode) {
|
|
checkExpression(extendsNode.expression);
|
|
}
|
|
|
|
const baseTypes = getBaseTypes(type);
|
|
if (baseTypes.length && produceDiagnostics) {
|
|
const baseType = baseTypes[0];
|
|
const baseConstructorType = getBaseConstructorTypeOfClass(type);
|
|
const staticBaseType = getApparentType(baseConstructorType);
|
|
checkBaseTypeAccessibility(staticBaseType, baseTypeNode);
|
|
checkSourceElement(baseTypeNode.expression);
|
|
if (some(baseTypeNode.typeArguments)) {
|
|
forEach(baseTypeNode.typeArguments, checkSourceElement);
|
|
for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) {
|
|
if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const baseWithThis = getTypeWithThisArgument(baseType, type.thisType);
|
|
if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) {
|
|
issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1);
|
|
}
|
|
else {
|
|
// Report static side error only when instance type is assignable
|
|
checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node,
|
|
Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1);
|
|
}
|
|
if (baseConstructorType.flags & TypeFlags.TypeVariable && !isMixinConstructorType(staticType)) {
|
|
error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any);
|
|
}
|
|
|
|
if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) {
|
|
// When the static base type is a "class-like" constructor function (but not actually a class), we verify
|
|
// that all instantiated base constructor signatures return the same type.
|
|
const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode);
|
|
if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) {
|
|
error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type);
|
|
}
|
|
}
|
|
checkKindsOfPropertyMemberOverrides(type, baseType);
|
|
}
|
|
}
|
|
|
|
const implementedTypeNodes = getEffectiveImplementsTypeNodes(node);
|
|
if (implementedTypeNodes) {
|
|
for (const typeRefNode of implementedTypeNodes) {
|
|
if (!isEntityNameExpression(typeRefNode.expression)) {
|
|
error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments);
|
|
}
|
|
checkTypeReferenceNode(typeRefNode);
|
|
if (produceDiagnostics) {
|
|
const t = getReducedType(getTypeFromTypeNode(typeRefNode));
|
|
if (t !== errorType) {
|
|
if (isValidBaseType(t)) {
|
|
const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ?
|
|
Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass :
|
|
Diagnostics.Class_0_incorrectly_implements_interface_1;
|
|
const baseWithThis = getTypeWithThisArgument(t, type.thisType);
|
|
if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) {
|
|
issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag);
|
|
}
|
|
}
|
|
else {
|
|
error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (produceDiagnostics) {
|
|
checkIndexConstraints(type);
|
|
checkTypeForDuplicateIndexSignatures(node);
|
|
checkPropertyInitialization(node);
|
|
}
|
|
}
|
|
|
|
function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) {
|
|
// iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible
|
|
let issuedMemberError = false;
|
|
for (const member of node.members) {
|
|
if (hasStaticModifier(member)) {
|
|
continue;
|
|
}
|
|
const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member);
|
|
if (declaredProp) {
|
|
const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName);
|
|
const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName);
|
|
if (prop && baseProp) {
|
|
const rootChain = () => chainDiagnosticMessages(
|
|
/*details*/ undefined,
|
|
Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2,
|
|
symbolToString(declaredProp),
|
|
typeToString(typeWithThis),
|
|
typeToString(baseWithThis)
|
|
);
|
|
if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) {
|
|
issuedMemberError = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!issuedMemberError) {
|
|
// check again with diagnostics to generate a less-specific error
|
|
checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag);
|
|
}
|
|
}
|
|
|
|
function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) {
|
|
const signatures = getSignaturesOfType(type, SignatureKind.Construct);
|
|
if (signatures.length) {
|
|
const declaration = signatures[0].declaration;
|
|
if (declaration && hasModifier(declaration, ModifierFlags.Private)) {
|
|
const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol)!;
|
|
if (!isNodeWithinClass(node, typeClassDeclaration)) {
|
|
error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getTargetSymbol(s: Symbol) {
|
|
// if symbol is instantiated its flags are not copied from the 'target'
|
|
// so we'll need to get back original 'target' symbol to work with correct set of flags
|
|
return getCheckFlags(s) & CheckFlags.Instantiated ? (<TransientSymbol>s).target! : s;
|
|
}
|
|
|
|
function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) {
|
|
return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration =>
|
|
d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration);
|
|
}
|
|
|
|
function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void {
|
|
// TypeScript 1.0 spec (April 2014): 8.2.3
|
|
// A derived class inherits all members from its base class it doesn't override.
|
|
// Inheritance means that a derived class implicitly contains all non - overridden members of the base class.
|
|
// Both public and private property members are inherited, but only public property members can be overridden.
|
|
// A property member in a derived class is said to override a property member in a base class
|
|
// when the derived class property member has the same name and kind(instance or static)
|
|
// as the base class property member.
|
|
// The type of an overriding property member must be assignable(section 3.8.4)
|
|
// to the type of the overridden property member, or otherwise a compile - time error occurs.
|
|
// Base class instance member functions can be overridden by derived class instance member functions,
|
|
// but not by other kinds of members.
|
|
// Base class instance member variables and accessors can be overridden by
|
|
// derived class instance member variables and accessors, but not by other kinds of members.
|
|
|
|
// NOTE: assignability is checked in checkClassDeclaration
|
|
const baseProperties = getPropertiesOfType(baseType);
|
|
basePropertyCheck: for (const baseProperty of baseProperties) {
|
|
const base = getTargetSymbol(baseProperty);
|
|
|
|
if (base.flags & SymbolFlags.Prototype) {
|
|
continue;
|
|
}
|
|
const baseSymbol = getPropertyOfObjectType(type, base.escapedName);
|
|
if (!baseSymbol) {
|
|
continue;
|
|
}
|
|
const derived = getTargetSymbol(baseSymbol);
|
|
const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base);
|
|
|
|
Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration.");
|
|
|
|
// In order to resolve whether the inherited method was overridden in the base class or not,
|
|
// we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated*
|
|
// type declaration, derived and base resolve to the same symbol even in the case of generic classes.
|
|
if (derived === base) {
|
|
// derived class inherits base without override/redeclaration
|
|
const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!;
|
|
|
|
// It is an error to inherit an abstract member without implementing it or being declared abstract.
|
|
// If there is no declaration for the derived class (as in the case of class expressions),
|
|
// then the class cannot be declared abstract.
|
|
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasModifier(derivedClassDecl, ModifierFlags.Abstract))) {
|
|
// Searches other base types for a declaration that would satisfy the inherited abstract member.
|
|
// (The class may have more than one base type via declaration merging with an interface with the
|
|
// same name.)
|
|
for (const otherBaseType of getBaseTypes(type)) {
|
|
if (otherBaseType === baseType) continue;
|
|
const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName);
|
|
const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol);
|
|
if (derivedElsewhere && derivedElsewhere !== base) {
|
|
continue basePropertyCheck;
|
|
}
|
|
}
|
|
|
|
if (derivedClassDecl.kind === SyntaxKind.ClassExpression) {
|
|
error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1,
|
|
symbolToString(baseProperty), typeToString(baseType));
|
|
}
|
|
else {
|
|
error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2,
|
|
typeToString(type), symbolToString(baseProperty), typeToString(baseType));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// derived overrides base.
|
|
const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived);
|
|
if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) {
|
|
// either base or derived property is private - not override, skip it
|
|
continue;
|
|
}
|
|
|
|
let errorMessage: DiagnosticMessage;
|
|
const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor;
|
|
const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor;
|
|
if (basePropertyFlags && derivedPropertyFlags) {
|
|
// property/accessor is overridden with property/accessor
|
|
if (!compilerOptions.useDefineForClassFields
|
|
|| baseDeclarationFlags & ModifierFlags.Abstract && !(base.valueDeclaration && isPropertyDeclaration(base.valueDeclaration) && base.valueDeclaration.initializer)
|
|
|| base.valueDeclaration && base.valueDeclaration.parent.kind === SyntaxKind.InterfaceDeclaration
|
|
|| derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) {
|
|
// when the base property is abstract or from an interface, base/derived flags don't need to match
|
|
// same when the derived property is from an assignment
|
|
continue;
|
|
}
|
|
|
|
const overriddenInstanceProperty = basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property;
|
|
const overriddenInstanceAccessor = basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property;
|
|
if (overriddenInstanceProperty || overriddenInstanceAccessor) {
|
|
const errorMessage = overriddenInstanceProperty ?
|
|
Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property :
|
|
Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor;
|
|
error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type));
|
|
}
|
|
else {
|
|
const uninitialized = find(derived.declarations, d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer);
|
|
if (uninitialized
|
|
&& !(derived.flags & SymbolFlags.Transient)
|
|
&& !(baseDeclarationFlags & ModifierFlags.Abstract)
|
|
&& !(derivedDeclarationFlags & ModifierFlags.Abstract)
|
|
&& !derived.declarations.some(d => !!(d.flags & NodeFlags.Ambient))) {
|
|
const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!);
|
|
const propName = (uninitialized as PropertyDeclaration).name;
|
|
if ((uninitialized as PropertyDeclaration).exclamationToken
|
|
|| !constructor
|
|
|| !isIdentifier(propName)
|
|
|| !strictNullChecks
|
|
|| !isPropertyInitializedInConstructor(propName, type, constructor)) {
|
|
const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration;
|
|
error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType));
|
|
}
|
|
}
|
|
}
|
|
|
|
// correct case
|
|
continue;
|
|
}
|
|
else if (isPrototypeProperty(base)) {
|
|
if (isPrototypeProperty(derived) || derived.flags & SymbolFlags.Property) {
|
|
// method is overridden with method or property -- correct case
|
|
continue;
|
|
}
|
|
else {
|
|
Debug.assert(!!(derived.flags & SymbolFlags.Accessor));
|
|
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor;
|
|
}
|
|
}
|
|
else if (base.flags & SymbolFlags.Accessor) {
|
|
errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function;
|
|
}
|
|
else {
|
|
errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function;
|
|
}
|
|
|
|
error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type));
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean {
|
|
const baseTypes = getBaseTypes(type);
|
|
if (baseTypes.length < 2) {
|
|
return true;
|
|
}
|
|
|
|
interface InheritanceInfoMap { prop: Symbol; containingType: Type; }
|
|
const seen = createUnderscoreEscapedMap<InheritanceInfoMap>();
|
|
forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen.set(p.escapedName, { prop: p, containingType: type }); });
|
|
let ok = true;
|
|
|
|
for (const base of baseTypes) {
|
|
const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType));
|
|
for (const prop of properties) {
|
|
const existing = seen.get(prop.escapedName);
|
|
if (!existing) {
|
|
seen.set(prop.escapedName, { prop, containingType: base });
|
|
}
|
|
else {
|
|
const isInheritedProperty = existing.containingType !== type;
|
|
if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) {
|
|
ok = false;
|
|
|
|
const typeName1 = typeToString(existing.containingType);
|
|
const typeName2 = typeToString(base);
|
|
|
|
let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2);
|
|
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2);
|
|
diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
function checkPropertyInitialization(node: ClassLikeDeclaration) {
|
|
if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) {
|
|
return;
|
|
}
|
|
const constructor = findConstructorDeclaration(node);
|
|
for (const member of node.members) {
|
|
if (getModifierFlags(member) & ModifierFlags.Ambient) {
|
|
continue;
|
|
}
|
|
if (isInstancePropertyWithoutInitializer(member)) {
|
|
const propName = (<PropertyDeclaration>member).name;
|
|
if (isIdentifier(propName) || isPrivateIdentifier(propName)) {
|
|
const type = getTypeOfSymbol(getSymbolOfNode(member));
|
|
if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) {
|
|
if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) {
|
|
error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isInstancePropertyWithoutInitializer(node: Node) {
|
|
return node.kind === SyntaxKind.PropertyDeclaration &&
|
|
!hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract) &&
|
|
!(<PropertyDeclaration>node).exclamationToken &&
|
|
!(<PropertyDeclaration>node).initializer;
|
|
}
|
|
|
|
function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier, propType: Type, constructor: ConstructorDeclaration) {
|
|
const reference = createPropertyAccess(createThis(), propName);
|
|
reference.expression.parent = reference;
|
|
reference.parent = constructor;
|
|
reference.flowNode = constructor.returnFlowNode;
|
|
const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType));
|
|
return !(getFalsyFlags(flowType) & TypeFlags.Undefined);
|
|
}
|
|
|
|
function checkInterfaceDeclaration(node: InterfaceDeclaration) {
|
|
// Grammar checking
|
|
if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node);
|
|
|
|
checkTypeParameters(node.typeParameters);
|
|
if (produceDiagnostics) {
|
|
checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0);
|
|
|
|
checkExportsOnMergedDeclarations(node);
|
|
const symbol = getSymbolOfNode(node);
|
|
checkTypeParameterListsIdentical(symbol);
|
|
|
|
// Only check this symbol once
|
|
const firstInterfaceDecl = getDeclarationOfKind<InterfaceDeclaration>(symbol, SyntaxKind.InterfaceDeclaration);
|
|
if (node === firstInterfaceDecl) {
|
|
const type = <InterfaceType>getDeclaredTypeOfSymbol(symbol);
|
|
const typeWithThis = getTypeWithThisArgument(type);
|
|
// run subsequent checks only if first set succeeded
|
|
if (checkInheritedPropertiesAreIdentical(type, node.name)) {
|
|
for (const baseType of getBaseTypes(type)) {
|
|
checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1);
|
|
}
|
|
checkIndexConstraints(type);
|
|
}
|
|
}
|
|
checkObjectTypeForDuplicateDeclarations(node);
|
|
}
|
|
forEach(getInterfaceBaseTypeNodes(node), heritageElement => {
|
|
if (!isEntityNameExpression(heritageElement.expression)) {
|
|
error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments);
|
|
}
|
|
checkTypeReferenceNode(heritageElement);
|
|
});
|
|
|
|
forEach(node.members, checkSourceElement);
|
|
|
|
if (produceDiagnostics) {
|
|
checkTypeForDuplicateIndexSignatures(node);
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
}
|
|
|
|
function checkTypeAliasDeclaration(node: TypeAliasDeclaration) {
|
|
// Grammar checking
|
|
checkGrammarDecoratorsAndModifiers(node);
|
|
|
|
checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0);
|
|
checkExportsOnMergedDeclarations(node);
|
|
checkTypeParameters(node.typeParameters);
|
|
checkSourceElement(node.type);
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
|
|
function computeEnumMemberValues(node: EnumDeclaration) {
|
|
const nodeLinks = getNodeLinks(node);
|
|
if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) {
|
|
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
|
|
let autoValue: number | undefined = 0;
|
|
for (const member of node.members) {
|
|
const value = computeMemberValue(member, autoValue);
|
|
getNodeLinks(member).enumMemberValue = value;
|
|
autoValue = typeof value === "number" ? value + 1 : undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
function computeMemberValue(member: EnumMember, autoValue: number | undefined) {
|
|
if (isComputedNonLiteralName(member.name)) {
|
|
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
|
|
}
|
|
else {
|
|
const text = getTextOfPropertyName(member.name);
|
|
if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) {
|
|
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
|
|
}
|
|
}
|
|
if (member.initializer) {
|
|
return computeConstantValue(member);
|
|
}
|
|
// In ambient non-const numeric enum declarations, enum members without initializers are
|
|
// considered computed members (as opposed to having auto-incremented values).
|
|
if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) {
|
|
return undefined;
|
|
}
|
|
// If the member declaration specifies no value, the member is considered a constant enum member.
|
|
// If the member is the first member in the enum declaration, it is assigned the value zero.
|
|
// Otherwise, it is assigned the value of the immediately preceding member plus one, and an error
|
|
// occurs if the immediately preceding member is not a constant enum member.
|
|
if (autoValue !== undefined) {
|
|
return autoValue;
|
|
}
|
|
error(member.name, Diagnostics.Enum_member_must_have_initializer);
|
|
return undefined;
|
|
}
|
|
|
|
function computeConstantValue(member: EnumMember): string | number | undefined {
|
|
const enumKind = getEnumKind(getSymbolOfNode(member.parent));
|
|
const isConstEnum = isEnumConst(member.parent);
|
|
const initializer = member.initializer!;
|
|
const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer);
|
|
if (value !== undefined) {
|
|
if (isConstEnum && typeof value === "number" && !isFinite(value)) {
|
|
error(initializer, isNaN(value) ?
|
|
Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN :
|
|
Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
|
|
}
|
|
}
|
|
else if (enumKind === EnumKind.Literal) {
|
|
error(initializer, Diagnostics.Computed_values_are_not_permitted_in_an_enum_with_string_valued_members);
|
|
return 0;
|
|
}
|
|
else if (isConstEnum) {
|
|
error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_computed_enum_values);
|
|
}
|
|
else if (member.parent.flags & NodeFlags.Ambient) {
|
|
error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression);
|
|
}
|
|
else {
|
|
// Only here do we need to check that the initializer is assignable to the enum type.
|
|
checkTypeAssignableTo(checkExpression(initializer), getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined);
|
|
}
|
|
return value;
|
|
|
|
function evaluate(expr: Expression): string | number | undefined {
|
|
switch (expr.kind) {
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
const value = evaluate((<PrefixUnaryExpression>expr).operand);
|
|
if (typeof value === "number") {
|
|
switch ((<PrefixUnaryExpression>expr).operator) {
|
|
case SyntaxKind.PlusToken: return value;
|
|
case SyntaxKind.MinusToken: return -value;
|
|
case SyntaxKind.TildeToken: return ~value;
|
|
}
|
|
}
|
|
break;
|
|
case SyntaxKind.BinaryExpression:
|
|
const left = evaluate((<BinaryExpression>expr).left);
|
|
const right = evaluate((<BinaryExpression>expr).right);
|
|
if (typeof left === "number" && typeof right === "number") {
|
|
switch ((<BinaryExpression>expr).operatorToken.kind) {
|
|
case SyntaxKind.BarToken: return left | right;
|
|
case SyntaxKind.AmpersandToken: return left & right;
|
|
case SyntaxKind.GreaterThanGreaterThanToken: return left >> right;
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right;
|
|
case SyntaxKind.LessThanLessThanToken: return left << right;
|
|
case SyntaxKind.CaretToken: return left ^ right;
|
|
case SyntaxKind.AsteriskToken: return left * right;
|
|
case SyntaxKind.SlashToken: return left / right;
|
|
case SyntaxKind.PlusToken: return left + right;
|
|
case SyntaxKind.MinusToken: return left - right;
|
|
case SyntaxKind.PercentToken: return left % right;
|
|
case SyntaxKind.AsteriskAsteriskToken: return left ** right;
|
|
}
|
|
}
|
|
else if (typeof left === "string" && typeof right === "string" && (<BinaryExpression>expr).operatorToken.kind === SyntaxKind.PlusToken) {
|
|
return left + right;
|
|
}
|
|
break;
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
return (<StringLiteralLike>expr).text;
|
|
case SyntaxKind.NumericLiteral:
|
|
checkGrammarNumericLiteral(<NumericLiteral>expr);
|
|
return +(<NumericLiteral>expr).text;
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return evaluate((<ParenthesizedExpression>expr).expression);
|
|
case SyntaxKind.Identifier:
|
|
const identifier = <Identifier>expr;
|
|
if (isInfinityOrNaNString(identifier.escapedText)) {
|
|
return +(identifier.escapedText);
|
|
}
|
|
return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText);
|
|
case SyntaxKind.ElementAccessExpression:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
const ex = <AccessExpression>expr;
|
|
if (isConstantMemberAccess(ex)) {
|
|
const type = getTypeOfExpression(ex.expression);
|
|
if (type.symbol && type.symbol.flags & SymbolFlags.Enum) {
|
|
let name: __String;
|
|
if (ex.kind === SyntaxKind.PropertyAccessExpression) {
|
|
name = ex.name.escapedText;
|
|
}
|
|
else {
|
|
name = escapeLeadingUnderscores(cast(ex.argumentExpression, isLiteralExpression).text);
|
|
}
|
|
return evaluateEnumMember(expr, type.symbol, name);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: __String) {
|
|
const memberSymbol = enumSymbol.exports!.get(name);
|
|
if (memberSymbol) {
|
|
const declaration = memberSymbol.valueDeclaration;
|
|
if (declaration !== member) {
|
|
if (isBlockScopedNameDeclaredBeforeUse(declaration, member)) {
|
|
return getEnumMemberValue(declaration as EnumMember);
|
|
}
|
|
error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums);
|
|
return 0;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function isConstantMemberAccess(node: Expression): boolean {
|
|
return node.kind === SyntaxKind.Identifier ||
|
|
node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((<PropertyAccessExpression>node).expression) ||
|
|
node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((<ElementAccessExpression>node).expression) &&
|
|
isStringLiteralLike((<ElementAccessExpression>node).argumentExpression);
|
|
}
|
|
|
|
function checkEnumDeclaration(node: EnumDeclaration) {
|
|
if (!produceDiagnostics) {
|
|
return;
|
|
}
|
|
|
|
// Grammar checking
|
|
checkGrammarDecoratorsAndModifiers(node);
|
|
|
|
checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
|
|
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
|
|
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
|
|
checkExportsOnMergedDeclarations(node);
|
|
node.members.forEach(checkEnumMember);
|
|
|
|
computeEnumMemberValues(node);
|
|
|
|
// Spec 2014 - Section 9.3:
|
|
// It isn't possible for one enum declaration to continue the automatic numbering sequence of another,
|
|
// and when an enum type has multiple declarations, only one declaration is permitted to omit a value
|
|
// for the first member.
|
|
//
|
|
// Only perform this check once per symbol
|
|
const enumSymbol = getSymbolOfNode(node);
|
|
const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind);
|
|
if (node === firstDeclaration) {
|
|
if (enumSymbol.declarations.length > 1) {
|
|
const enumIsConst = isEnumConst(node);
|
|
// check that const is placed\omitted on all enum declarations
|
|
forEach(enumSymbol.declarations, decl => {
|
|
if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) {
|
|
error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const);
|
|
}
|
|
});
|
|
}
|
|
|
|
let seenEnumMissingInitialInitializer = false;
|
|
forEach(enumSymbol.declarations, declaration => {
|
|
// return true if we hit a violation of the rule, false otherwise
|
|
if (declaration.kind !== SyntaxKind.EnumDeclaration) {
|
|
return false;
|
|
}
|
|
|
|
const enumDeclaration = <EnumDeclaration>declaration;
|
|
if (!enumDeclaration.members.length) {
|
|
return false;
|
|
}
|
|
|
|
const firstEnumMember = enumDeclaration.members[0];
|
|
if (!firstEnumMember.initializer) {
|
|
if (seenEnumMissingInitialInitializer) {
|
|
error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element);
|
|
}
|
|
else {
|
|
seenEnumMissingInitialInitializer = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function checkEnumMember(node: EnumMember) {
|
|
if (isPrivateIdentifier(node.name)) {
|
|
error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier);
|
|
}
|
|
}
|
|
|
|
function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined {
|
|
const declarations = symbol.declarations;
|
|
for (const declaration of declarations) {
|
|
if ((declaration.kind === SyntaxKind.ClassDeclaration ||
|
|
(declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((<FunctionLikeDeclaration>declaration).body))) &&
|
|
!(declaration.flags & NodeFlags.Ambient)) {
|
|
return declaration;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function inSameLexicalScope(node1: Node, node2: Node) {
|
|
const container1 = getEnclosingBlockScopeContainer(node1);
|
|
const container2 = getEnclosingBlockScopeContainer(node2);
|
|
if (isGlobalSourceFile(container1)) {
|
|
return isGlobalSourceFile(container2);
|
|
}
|
|
else if (isGlobalSourceFile(container2)) {
|
|
return false;
|
|
}
|
|
else {
|
|
return container1 === container2;
|
|
}
|
|
}
|
|
|
|
function checkModuleDeclaration(node: ModuleDeclaration) {
|
|
if (produceDiagnostics) {
|
|
// Grammar checking
|
|
const isGlobalAugmentation = isGlobalScopeAugmentation(node);
|
|
const inAmbientContext = node.flags & NodeFlags.Ambient;
|
|
if (isGlobalAugmentation && !inAmbientContext) {
|
|
error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context);
|
|
}
|
|
|
|
const isAmbientExternalModule = isAmbientModule(node);
|
|
const contextErrorMessage = isAmbientExternalModule
|
|
? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file
|
|
: Diagnostics.A_namespace_declaration_is_only_allowed_in_a_namespace_or_module;
|
|
if (checkGrammarModuleElementContext(node, contextErrorMessage)) {
|
|
// If we hit a module declaration in an illegal context, just bail out to avoid cascading errors.
|
|
return;
|
|
}
|
|
|
|
if (!checkGrammarDecoratorsAndModifiers(node)) {
|
|
if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) {
|
|
grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names);
|
|
}
|
|
}
|
|
|
|
if (isIdentifier(node.name)) {
|
|
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
|
|
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
|
|
}
|
|
|
|
checkExportsOnMergedDeclarations(node);
|
|
const symbol = getSymbolOfNode(node);
|
|
|
|
// The following checks only apply on a non-ambient instantiated module declaration.
|
|
if (symbol.flags & SymbolFlags.ValueModule
|
|
&& !inAmbientContext
|
|
&& symbol.declarations.length > 1
|
|
&& isInstantiatedModule(node, !!compilerOptions.preserveConstEnums || !!compilerOptions.isolatedModules)) {
|
|
const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol);
|
|
if (firstNonAmbientClassOrFunc) {
|
|
if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) {
|
|
error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged);
|
|
}
|
|
else if (node.pos < firstNonAmbientClassOrFunc.pos) {
|
|
error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged);
|
|
}
|
|
}
|
|
|
|
// if the module merges with a class declaration in the same lexical scope,
|
|
// we need to track this to ensure the correct emit.
|
|
const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration);
|
|
if (mergedClass &&
|
|
inSameLexicalScope(node, mergedClass)) {
|
|
getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass;
|
|
}
|
|
}
|
|
|
|
if (isAmbientExternalModule) {
|
|
if (isExternalModuleAugmentation(node)) {
|
|
// body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module)
|
|
// otherwise we'll be swamped in cascading errors.
|
|
// We can detect if augmentation was applied using following rules:
|
|
// - augmentation for a global scope is always applied
|
|
// - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module).
|
|
const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient);
|
|
if (checkBody && node.body) {
|
|
for (const statement of node.body.statements) {
|
|
checkModuleAugmentationElement(statement, isGlobalAugmentation);
|
|
}
|
|
}
|
|
}
|
|
else if (isGlobalSourceFile(node.parent)) {
|
|
if (isGlobalAugmentation) {
|
|
error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations);
|
|
}
|
|
else if (isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(node.name))) {
|
|
error(node.name, Diagnostics.Ambient_module_declaration_cannot_specify_relative_module_name);
|
|
}
|
|
}
|
|
else {
|
|
if (isGlobalAugmentation) {
|
|
error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations);
|
|
}
|
|
else {
|
|
// Node is not an augmentation and is not located on the script level.
|
|
// This means that this is declaration of ambient module that is located in other module or namespace which is prohibited.
|
|
error(node.name, Diagnostics.Ambient_modules_cannot_be_nested_in_other_modules_or_namespaces);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node.body) {
|
|
checkSourceElement(node.body);
|
|
if (!isGlobalScopeAugmentation(node)) {
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void {
|
|
switch (node.kind) {
|
|
case SyntaxKind.VariableStatement:
|
|
// error each individual name in variable statement instead of marking the entire variable statement
|
|
for (const decl of (<VariableStatement>node).declarationList.declarations) {
|
|
checkModuleAugmentationElement(decl, isGlobalAugmentation);
|
|
}
|
|
break;
|
|
case SyntaxKind.ExportAssignment:
|
|
case SyntaxKind.ExportDeclaration:
|
|
grammarErrorOnFirstToken(node, Diagnostics.Exports_and_export_assignments_are_not_permitted_in_module_augmentations);
|
|
break;
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
case SyntaxKind.ImportDeclaration:
|
|
grammarErrorOnFirstToken(node, Diagnostics.Imports_are_not_permitted_in_module_augmentations_Consider_moving_them_to_the_enclosing_external_module);
|
|
break;
|
|
case SyntaxKind.BindingElement:
|
|
case SyntaxKind.VariableDeclaration:
|
|
const name = (<VariableDeclaration | BindingElement>node).name;
|
|
if (isBindingPattern(name)) {
|
|
for (const el of name.elements) {
|
|
// mark individual names in binding pattern
|
|
checkModuleAugmentationElement(el, isGlobalAugmentation);
|
|
}
|
|
break;
|
|
}
|
|
// falls through
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
if (isGlobalAugmentation) {
|
|
return;
|
|
}
|
|
const symbol = getSymbolOfNode(node);
|
|
if (symbol) {
|
|
// module augmentations cannot introduce new names on the top level scope of the module
|
|
// this is done it two steps
|
|
// 1. quick check - if symbol for node is not merged - this is local symbol to this augmentation - report error
|
|
// 2. main check - report error if value declaration of the parent symbol is module augmentation)
|
|
let reportError = !(symbol.flags & SymbolFlags.Transient);
|
|
if (!reportError) {
|
|
// symbol should not originate in augmentation
|
|
reportError = !!symbol.parent && isExternalModuleAugmentation(symbol.parent.declarations[0]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function getFirstNonModuleExportsIdentifier(node: EntityNameOrEntityNameExpression): Identifier {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return node;
|
|
case SyntaxKind.QualifiedName:
|
|
do {
|
|
node = node.left;
|
|
} while (node.kind !== SyntaxKind.Identifier);
|
|
return node;
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
do {
|
|
if (isModuleExportsAccessExpression(node.expression) && !isPrivateIdentifier(node.name)) {
|
|
return node.name;
|
|
}
|
|
node = node.expression;
|
|
} while (node.kind !== SyntaxKind.Identifier);
|
|
return node;
|
|
}
|
|
}
|
|
|
|
function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean {
|
|
const moduleName = getExternalModuleName(node);
|
|
if (!moduleName || nodeIsMissing(moduleName)) {
|
|
// Should be a parse error.
|
|
return false;
|
|
}
|
|
if (!isStringLiteral(moduleName)) {
|
|
error(moduleName, Diagnostics.String_literal_expected);
|
|
return false;
|
|
}
|
|
const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent);
|
|
if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule) {
|
|
error(moduleName, node.kind === SyntaxKind.ExportDeclaration ?
|
|
Diagnostics.Export_declarations_are_not_permitted_in_a_namespace :
|
|
Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module);
|
|
return false;
|
|
}
|
|
if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) {
|
|
// we have already reported errors on top level imports\exports in external module augmentations in checkModuleDeclaration
|
|
// no need to do this again.
|
|
if (!isTopLevelInExternalModuleAugmentation(node)) {
|
|
// TypeScript 1.0 spec (April 2013): 12.1.6
|
|
// An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference
|
|
// other external modules only through top - level external module names.
|
|
// Relative external module names are not permitted.
|
|
error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function checkAliasSymbol(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport) {
|
|
let symbol = getSymbolOfNode(node);
|
|
const target = resolveAlias(symbol);
|
|
|
|
const shouldSkipWithJSExpandoTargets = symbol.flags & SymbolFlags.Assignment;
|
|
if (!shouldSkipWithJSExpandoTargets && target !== unknownSymbol) {
|
|
// For external modules symbol represents local symbol for an alias.
|
|
// This local symbol will merge any other local declarations (excluding other aliases)
|
|
// and symbol.flags will contains combined representation for all merged declaration.
|
|
// Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have,
|
|
// otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export*
|
|
// in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names).
|
|
symbol = getMergedSymbol(symbol.exportSymbol || symbol);
|
|
const excludedMeanings =
|
|
(symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) |
|
|
(symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) |
|
|
(symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0);
|
|
if (target.flags & excludedMeanings) {
|
|
const message = node.kind === SyntaxKind.ExportSpecifier ?
|
|
Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 :
|
|
Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0;
|
|
error(node, message, symbolToString(symbol));
|
|
}
|
|
|
|
// Don't allow to re-export something with no value side when `--isolatedModules` is set.
|
|
if (compilerOptions.isolatedModules
|
|
&& node.kind === SyntaxKind.ExportSpecifier
|
|
&& !node.parent.parent.isTypeOnly
|
|
&& !(target.flags & SymbolFlags.Value)
|
|
&& !(node.flags & NodeFlags.Ambient)) {
|
|
error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) {
|
|
checkCollisionWithRequireExportsInGeneratedCode(node, node.name!);
|
|
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!);
|
|
checkAliasSymbol(node);
|
|
}
|
|
|
|
function checkImportDeclaration(node: ImportDeclaration) {
|
|
if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) {
|
|
// If we hit an import declaration in an illegal context, just bail out to avoid cascading errors.
|
|
return;
|
|
}
|
|
if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) {
|
|
grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers);
|
|
}
|
|
if (checkExternalImportOrExportDeclaration(node)) {
|
|
const importClause = node.importClause;
|
|
if (importClause && !checkGrammarImportClause(importClause)) {
|
|
if (importClause.name) {
|
|
checkImportBinding(importClause);
|
|
}
|
|
if (importClause.namedBindings) {
|
|
if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) {
|
|
checkImportBinding(importClause.namedBindings);
|
|
}
|
|
else {
|
|
const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier);
|
|
if (moduleExisted) {
|
|
forEach(importClause.namedBindings.elements, checkImportBinding);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) {
|
|
if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) {
|
|
// If we hit an import declaration in an illegal context, just bail out to avoid cascading errors.
|
|
return;
|
|
}
|
|
|
|
checkGrammarDecoratorsAndModifiers(node);
|
|
if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) {
|
|
checkImportBinding(node);
|
|
if (hasModifier(node, ModifierFlags.Export)) {
|
|
markExportAsReferenced(node);
|
|
}
|
|
if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) {
|
|
const target = resolveAlias(getSymbolOfNode(node));
|
|
if (target !== unknownSymbol) {
|
|
if (target.flags & SymbolFlags.Value) {
|
|
// Target is a value symbol, check that it is not hidden by a local declaration with the same name
|
|
const moduleName = getFirstIdentifier(node.moduleReference);
|
|
if (!(resolveEntityName(moduleName, SymbolFlags.Value | SymbolFlags.Namespace)!.flags & SymbolFlags.Namespace)) {
|
|
error(moduleName, Diagnostics.Module_0_is_hidden_by_a_local_declaration_with_the_same_name, declarationNameToString(moduleName));
|
|
}
|
|
}
|
|
if (target.flags & SymbolFlags.Type) {
|
|
checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (moduleKind >= ModuleKind.ES2015 && !(node.flags & NodeFlags.Ambient)) {
|
|
// Import equals declaration is deprecated in es6 or above
|
|
grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkExportDeclaration(node: ExportDeclaration) {
|
|
if (checkGrammarModuleElementContext(node, Diagnostics.An_export_declaration_can_only_be_used_in_a_module)) {
|
|
// If we hit an export in an illegal context, just bail out to avoid cascading errors.
|
|
return;
|
|
}
|
|
|
|
if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) {
|
|
grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers);
|
|
}
|
|
|
|
if (node.moduleSpecifier && node.exportClause && isNamedExports(node.exportClause) && length(node.exportClause.elements) && languageVersion === ScriptTarget.ES3) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding);
|
|
}
|
|
|
|
checkGrammarExportDeclaration(node);
|
|
if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) {
|
|
if (node.exportClause) {
|
|
// export { x, y }
|
|
// export { x, y } from "foo"
|
|
if (isNamedExports(node.exportClause)) {
|
|
forEach(node.exportClause.elements, checkExportSpecifier);
|
|
}
|
|
else if(!isNamespaceExport(node.exportClause)) {
|
|
checkImportBinding(node.exportClause);
|
|
}
|
|
|
|
const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent);
|
|
const inAmbientNamespaceDeclaration = !inAmbientExternalModule && node.parent.kind === SyntaxKind.ModuleBlock &&
|
|
!node.moduleSpecifier && node.flags & NodeFlags.Ambient;
|
|
if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule && !inAmbientNamespaceDeclaration) {
|
|
error(node, Diagnostics.Export_declarations_are_not_permitted_in_a_namespace);
|
|
}
|
|
}
|
|
else {
|
|
// export * from "foo"
|
|
const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!);
|
|
if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) {
|
|
error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol));
|
|
}
|
|
|
|
if (moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015) {
|
|
checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarExportDeclaration(node: ExportDeclaration): boolean {
|
|
const isTypeOnlyExportStar = node.isTypeOnly && node.exportClause?.kind !== SyntaxKind.NamedExports;
|
|
if (isTypeOnlyExportStar) {
|
|
grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type);
|
|
}
|
|
return !isTypeOnlyExportStar;
|
|
}
|
|
|
|
function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean {
|
|
const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration;
|
|
if (!isInAppropriateContext) {
|
|
grammarErrorOnFirstToken(node, errorMessage);
|
|
}
|
|
return !isInAppropriateContext;
|
|
}
|
|
|
|
function importClauseContainsReferencedImport(importClause: ImportClause) {
|
|
return forEachImportClauseDeclaration(importClause, declaration => {
|
|
return !!getSymbolOfNode(declaration).isReferenced;
|
|
});
|
|
}
|
|
|
|
function importClauseContainsConstEnumUsedAsValue(importClause: ImportClause) {
|
|
return forEachImportClauseDeclaration(importClause, declaration => {
|
|
return !!getSymbolLinks(getSymbolOfNode(declaration)).constEnumReferenced;
|
|
});
|
|
}
|
|
|
|
function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) {
|
|
for (const statement of sourceFile.statements) {
|
|
if (
|
|
isImportDeclaration(statement) &&
|
|
statement.importClause &&
|
|
!statement.importClause.isTypeOnly &&
|
|
importClauseContainsReferencedImport(statement.importClause) &&
|
|
!isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) &&
|
|
!importClauseContainsConstEnumUsedAsValue(statement.importClause)
|
|
) {
|
|
error(
|
|
statement,
|
|
Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_the_importsNotUsedAsValues_is_set_to_error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkExportSpecifier(node: ExportSpecifier) {
|
|
checkAliasSymbol(node);
|
|
if (getEmitDeclarations(compilerOptions)) {
|
|
collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true);
|
|
}
|
|
if (!node.parent.parent.moduleSpecifier) {
|
|
const exportedName = node.propertyName || node.name;
|
|
// find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases)
|
|
const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias,
|
|
/*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
|
|
if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) {
|
|
error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName));
|
|
}
|
|
else {
|
|
markExportAsReferenced(node);
|
|
const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol);
|
|
if (!target || target === unknownSymbol || target.flags & SymbolFlags.Value) {
|
|
checkExpressionCached(node.propertyName || node.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkExportAssignment(node: ExportAssignment) {
|
|
if (checkGrammarModuleElementContext(node, Diagnostics.An_export_assignment_can_only_be_used_in_a_module)) {
|
|
// If we hit an export assignment in an illegal context, just bail out to avoid cascading errors.
|
|
return;
|
|
}
|
|
|
|
const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : <ModuleDeclaration>node.parent.parent;
|
|
if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) {
|
|
if (node.isExportEquals) {
|
|
error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_namespace);
|
|
}
|
|
else {
|
|
error(node, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module);
|
|
}
|
|
|
|
return;
|
|
}
|
|
// Grammar checking
|
|
if (!checkGrammarDecoratorsAndModifiers(node) && hasModifiers(node)) {
|
|
grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers);
|
|
}
|
|
if (node.expression.kind === SyntaxKind.Identifier) {
|
|
const id = node.expression as Identifier;
|
|
const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node);
|
|
if (sym) {
|
|
|
|
markAliasReferenced(sym, id);
|
|
// If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`)
|
|
const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym;
|
|
if (target === unknownSymbol || target.flags & SymbolFlags.Value) {
|
|
// However if it is a value, we need to check it's being used correctly
|
|
checkExpressionCached(node.expression);
|
|
}
|
|
}
|
|
|
|
if (getEmitDeclarations(compilerOptions)) {
|
|
collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true);
|
|
}
|
|
}
|
|
else {
|
|
checkExpressionCached(node.expression);
|
|
}
|
|
|
|
checkExternalModuleExports(container);
|
|
|
|
if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) {
|
|
grammarErrorOnNode(node.expression, Diagnostics.The_expression_of_an_export_assignment_must_be_an_identifier_or_qualified_name_in_an_ambient_context);
|
|
}
|
|
|
|
if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) {
|
|
if (moduleKind >= ModuleKind.ES2015) {
|
|
// export assignment is not supported in es6 modules
|
|
grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead);
|
|
}
|
|
else if (moduleKind === ModuleKind.System) {
|
|
// system modules does not support export assignment
|
|
grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system);
|
|
}
|
|
}
|
|
}
|
|
|
|
function hasExportedMembers(moduleSymbol: Symbol) {
|
|
return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export=");
|
|
}
|
|
|
|
function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) {
|
|
const moduleSymbol = getSymbolOfNode(node);
|
|
const links = getSymbolLinks(moduleSymbol);
|
|
if (!links.exportsChecked) {
|
|
const exportEqualsSymbol = moduleSymbol.exports!.get("export=" as __String);
|
|
if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) {
|
|
const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration;
|
|
if (!isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) {
|
|
error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements);
|
|
}
|
|
}
|
|
// Checks for export * conflicts
|
|
const exports = getExportsOfModule(moduleSymbol);
|
|
if (exports) {
|
|
exports.forEach(({ declarations, flags }, id) => {
|
|
if (id === "__export") {
|
|
return;
|
|
}
|
|
// ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries.
|
|
// (TS Exceptions: namespaces, function overloads, enums, and interfaces)
|
|
if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) {
|
|
return;
|
|
}
|
|
const exportedDeclarationsCount = countWhere(declarations, isNotOverloadAndNotAccessor);
|
|
if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) {
|
|
// it is legal to merge type alias with other values
|
|
// so count should be either 1 (just type alias) or 2 (type alias + merged value)
|
|
return;
|
|
}
|
|
if (exportedDeclarationsCount > 1) {
|
|
for (const declaration of declarations) {
|
|
if (isNotOverload(declaration)) {
|
|
diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id)));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
links.exportsChecked = true;
|
|
}
|
|
}
|
|
|
|
function checkSourceElement(node: Node | undefined): void {
|
|
if (node) {
|
|
const saveCurrentNode = currentNode;
|
|
currentNode = node;
|
|
instantiationCount = 0;
|
|
checkSourceElementWorker(node);
|
|
currentNode = saveCurrentNode;
|
|
}
|
|
}
|
|
|
|
function checkSourceElementWorker(node: Node): void {
|
|
if (isInJSFile(node)) {
|
|
forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement));
|
|
}
|
|
|
|
const kind = node.kind;
|
|
if (cancellationToken) {
|
|
// Only bother checking on a few construct kinds. We don't want to be excessively
|
|
// hitting the cancellation token on every node we check.
|
|
switch (kind) {
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
cancellationToken.throwIfCancellationRequested();
|
|
}
|
|
}
|
|
if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) {
|
|
errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected);
|
|
}
|
|
|
|
switch (kind) {
|
|
case SyntaxKind.TypeParameter:
|
|
return checkTypeParameter(<TypeParameterDeclaration>node);
|
|
case SyntaxKind.Parameter:
|
|
return checkParameter(<ParameterDeclaration>node);
|
|
case SyntaxKind.PropertyDeclaration:
|
|
return checkPropertyDeclaration(<PropertyDeclaration>node);
|
|
case SyntaxKind.PropertySignature:
|
|
return checkPropertySignature(<PropertySignature>node);
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.IndexSignature:
|
|
return checkSignatureDeclaration(<SignatureDeclaration>node);
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
return checkMethodDeclaration(<MethodDeclaration | MethodSignature>node);
|
|
case SyntaxKind.Constructor:
|
|
return checkConstructorDeclaration(<ConstructorDeclaration>node);
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
return checkAccessorDeclaration(<AccessorDeclaration>node);
|
|
case SyntaxKind.TypeReference:
|
|
return checkTypeReferenceNode(<TypeReferenceNode>node);
|
|
case SyntaxKind.TypePredicate:
|
|
return checkTypePredicate(<TypePredicateNode>node);
|
|
case SyntaxKind.TypeQuery:
|
|
return checkTypeQuery(<TypeQueryNode>node);
|
|
case SyntaxKind.TypeLiteral:
|
|
return checkTypeLiteral(<TypeLiteralNode>node);
|
|
case SyntaxKind.ArrayType:
|
|
return checkArrayType(<ArrayTypeNode>node);
|
|
case SyntaxKind.TupleType:
|
|
return checkTupleType(<TupleTypeNode>node);
|
|
case SyntaxKind.UnionType:
|
|
case SyntaxKind.IntersectionType:
|
|
return checkUnionOrIntersectionType(<UnionOrIntersectionTypeNode>node);
|
|
case SyntaxKind.ParenthesizedType:
|
|
case SyntaxKind.OptionalType:
|
|
case SyntaxKind.RestType:
|
|
return checkSourceElement((<ParenthesizedTypeNode | OptionalTypeNode | RestTypeNode>node).type);
|
|
case SyntaxKind.ThisType:
|
|
return checkThisType(<ThisTypeNode>node);
|
|
case SyntaxKind.TypeOperator:
|
|
return checkTypeOperator(<TypeOperatorNode>node);
|
|
case SyntaxKind.ConditionalType:
|
|
return checkConditionalType(<ConditionalTypeNode>node);
|
|
case SyntaxKind.InferType:
|
|
return checkInferType(<InferTypeNode>node);
|
|
case SyntaxKind.ImportType:
|
|
return checkImportType(<ImportTypeNode>node);
|
|
case SyntaxKind.JSDocAugmentsTag:
|
|
return checkJSDocAugmentsTag(node as JSDocAugmentsTag);
|
|
case SyntaxKind.JSDocImplementsTag:
|
|
return checkJSDocImplementsTag(node as JSDocImplementsTag);
|
|
case SyntaxKind.JSDocTypedefTag:
|
|
case SyntaxKind.JSDocCallbackTag:
|
|
case SyntaxKind.JSDocEnumTag:
|
|
return checkJSDocTypeAliasTag(node as JSDocTypedefTag);
|
|
case SyntaxKind.JSDocTemplateTag:
|
|
return checkJSDocTemplateTag(node as JSDocTemplateTag);
|
|
case SyntaxKind.JSDocTypeTag:
|
|
return checkJSDocTypeTag(node as JSDocTypeTag);
|
|
case SyntaxKind.JSDocParameterTag:
|
|
return checkJSDocParameterTag(node as JSDocParameterTag);
|
|
case SyntaxKind.JSDocFunctionType:
|
|
checkJSDocFunctionType(node as JSDocFunctionType);
|
|
// falls through
|
|
case SyntaxKind.JSDocNonNullableType:
|
|
case SyntaxKind.JSDocNullableType:
|
|
case SyntaxKind.JSDocAllType:
|
|
case SyntaxKind.JSDocUnknownType:
|
|
case SyntaxKind.JSDocTypeLiteral:
|
|
checkJSDocTypeIsInJsFile(node);
|
|
forEachChild(node, checkSourceElement);
|
|
return;
|
|
case SyntaxKind.JSDocVariadicType:
|
|
checkJSDocVariadicType(node as JSDocVariadicType);
|
|
return;
|
|
case SyntaxKind.JSDocTypeExpression:
|
|
return checkSourceElement((node as JSDocTypeExpression).type);
|
|
case SyntaxKind.IndexedAccessType:
|
|
return checkIndexedAccessType(<IndexedAccessTypeNode>node);
|
|
case SyntaxKind.MappedType:
|
|
return checkMappedType(<MappedTypeNode>node);
|
|
case SyntaxKind.FunctionDeclaration:
|
|
return checkFunctionDeclaration(<FunctionDeclaration>node);
|
|
case SyntaxKind.Block:
|
|
case SyntaxKind.ModuleBlock:
|
|
return checkBlock(<Block>node);
|
|
case SyntaxKind.VariableStatement:
|
|
return checkVariableStatement(<VariableStatement>node);
|
|
case SyntaxKind.ExpressionStatement:
|
|
return checkExpressionStatement(<ExpressionStatement>node);
|
|
case SyntaxKind.IfStatement:
|
|
return checkIfStatement(<IfStatement>node);
|
|
case SyntaxKind.DoStatement:
|
|
return checkDoStatement(<DoStatement>node);
|
|
case SyntaxKind.WhileStatement:
|
|
return checkWhileStatement(<WhileStatement>node);
|
|
case SyntaxKind.ForStatement:
|
|
return checkForStatement(<ForStatement>node);
|
|
case SyntaxKind.ForInStatement:
|
|
return checkForInStatement(<ForInStatement>node);
|
|
case SyntaxKind.ForOfStatement:
|
|
return checkForOfStatement(<ForOfStatement>node);
|
|
case SyntaxKind.ContinueStatement:
|
|
case SyntaxKind.BreakStatement:
|
|
return checkBreakOrContinueStatement(<BreakOrContinueStatement>node);
|
|
case SyntaxKind.ReturnStatement:
|
|
return checkReturnStatement(<ReturnStatement>node);
|
|
case SyntaxKind.WithStatement:
|
|
return checkWithStatement(<WithStatement>node);
|
|
case SyntaxKind.SwitchStatement:
|
|
return checkSwitchStatement(<SwitchStatement>node);
|
|
case SyntaxKind.LabeledStatement:
|
|
return checkLabeledStatement(<LabeledStatement>node);
|
|
case SyntaxKind.ThrowStatement:
|
|
return checkThrowStatement(<ThrowStatement>node);
|
|
case SyntaxKind.TryStatement:
|
|
return checkTryStatement(<TryStatement>node);
|
|
case SyntaxKind.VariableDeclaration:
|
|
return checkVariableDeclaration(<VariableDeclaration>node);
|
|
case SyntaxKind.BindingElement:
|
|
return checkBindingElement(<BindingElement>node);
|
|
case SyntaxKind.ClassDeclaration:
|
|
return checkClassDeclaration(<ClassDeclaration>node);
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
return checkInterfaceDeclaration(<InterfaceDeclaration>node);
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
return checkTypeAliasDeclaration(<TypeAliasDeclaration>node);
|
|
case SyntaxKind.EnumDeclaration:
|
|
return checkEnumDeclaration(<EnumDeclaration>node);
|
|
case SyntaxKind.ModuleDeclaration:
|
|
return checkModuleDeclaration(<ModuleDeclaration>node);
|
|
case SyntaxKind.ImportDeclaration:
|
|
return checkImportDeclaration(<ImportDeclaration>node);
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
return checkImportEqualsDeclaration(<ImportEqualsDeclaration>node);
|
|
case SyntaxKind.ExportDeclaration:
|
|
return checkExportDeclaration(<ExportDeclaration>node);
|
|
case SyntaxKind.ExportAssignment:
|
|
return checkExportAssignment(<ExportAssignment>node);
|
|
case SyntaxKind.EmptyStatement:
|
|
case SyntaxKind.DebuggerStatement:
|
|
checkGrammarStatementInAmbientContext(node);
|
|
return;
|
|
case SyntaxKind.MissingDeclaration:
|
|
return checkMissingDeclaration(node);
|
|
}
|
|
}
|
|
|
|
function checkJSDocTypeIsInJsFile(node: Node): void {
|
|
if (!isInJSFile(node)) {
|
|
grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments);
|
|
}
|
|
}
|
|
|
|
function checkJSDocVariadicType(node: JSDocVariadicType): void {
|
|
checkJSDocTypeIsInJsFile(node);
|
|
checkSourceElement(node.type);
|
|
|
|
// Only legal location is in the *last* parameter tag or last parameter of a JSDoc function.
|
|
const { parent } = node;
|
|
if (isParameter(parent) && isJSDocFunctionType(parent.parent)) {
|
|
if (last(parent.parent.parameters) !== parent) {
|
|
error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!isJSDocTypeExpression(parent)) {
|
|
error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature);
|
|
}
|
|
|
|
const paramTag = node.parent.parent;
|
|
if (!isJSDocParameterTag(paramTag)) {
|
|
error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature);
|
|
return;
|
|
}
|
|
|
|
const param = getParameterSymbolFromJSDoc(paramTag);
|
|
if (!param) {
|
|
// We will error in `checkJSDocParameterTag`.
|
|
return;
|
|
}
|
|
|
|
const host = getHostSignatureFromJSDoc(paramTag);
|
|
if (!host || last(host.parameters).symbol !== param) {
|
|
error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list);
|
|
}
|
|
}
|
|
|
|
function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type {
|
|
const type = getTypeFromTypeNode(node.type);
|
|
const { parent } = node;
|
|
const paramTag = node.parent.parent;
|
|
if (isJSDocTypeExpression(node.parent) && isJSDocParameterTag(paramTag)) {
|
|
// Else we will add a diagnostic, see `checkJSDocVariadicType`.
|
|
const host = getHostSignatureFromJSDoc(paramTag);
|
|
if (host) {
|
|
/*
|
|
Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters.
|
|
So in the following situation we will not create an array type:
|
|
/** @param {...number} a * /
|
|
function f(a) {}
|
|
Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type.
|
|
*/
|
|
const lastParamDeclaration = lastOrUndefined(host.parameters);
|
|
const symbol = getParameterSymbolFromJSDoc(paramTag);
|
|
if (!lastParamDeclaration ||
|
|
symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) {
|
|
return createArrayType(type);
|
|
}
|
|
}
|
|
}
|
|
if (isParameter(parent) && isJSDocFunctionType(parent.parent)) {
|
|
return createArrayType(type);
|
|
}
|
|
return addOptionality(type);
|
|
}
|
|
|
|
// Function and class expression bodies are checked after all statements in the enclosing body. This is
|
|
// to ensure constructs like the following are permitted:
|
|
// const foo = function () {
|
|
// const s = foo();
|
|
// return "hello";
|
|
// }
|
|
// Here, performing a full type check of the body of the function expression whilst in the process of
|
|
// determining the type of foo would cause foo to be given type any because of the recursive reference.
|
|
// Delaying the type check of the body ensures foo has been assigned a type.
|
|
function checkNodeDeferred(node: Node) {
|
|
const enclosingFile = getSourceFileOfNode(node);
|
|
const links = getNodeLinks(enclosingFile);
|
|
if (!(links.flags & NodeCheckFlags.TypeChecked)) {
|
|
links.deferredNodes = links.deferredNodes || createMap();
|
|
const id = "" + getNodeId(node);
|
|
links.deferredNodes.set(id, node);
|
|
}
|
|
}
|
|
|
|
function checkDeferredNodes(context: SourceFile) {
|
|
const links = getNodeLinks(context);
|
|
if (links.deferredNodes) {
|
|
links.deferredNodes.forEach(checkDeferredNode);
|
|
}
|
|
}
|
|
|
|
function checkDeferredNode(node: Node) {
|
|
const saveCurrentNode = currentNode;
|
|
currentNode = node;
|
|
instantiationCount = 0;
|
|
switch (node.kind) {
|
|
case SyntaxKind.CallExpression:
|
|
case SyntaxKind.NewExpression:
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
case SyntaxKind.Decorator:
|
|
case SyntaxKind.JsxOpeningElement:
|
|
// These node kinds are deferred checked when overload resolution fails
|
|
// To save on work, we ensure the arguments are checked just once, in
|
|
// a deferred way
|
|
resolveUntypedCall(node as CallLikeExpression);
|
|
break;
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
checkFunctionExpressionOrObjectLiteralMethodDeferred(<FunctionExpression>node);
|
|
break;
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
checkAccessorDeclaration(<AccessorDeclaration>node);
|
|
break;
|
|
case SyntaxKind.ClassExpression:
|
|
checkClassExpressionDeferred(<ClassExpression>node);
|
|
break;
|
|
case SyntaxKind.JsxSelfClosingElement:
|
|
checkJsxSelfClosingElementDeferred(<JsxSelfClosingElement>node);
|
|
break;
|
|
case SyntaxKind.JsxElement:
|
|
checkJsxElementDeferred(<JsxElement>node);
|
|
break;
|
|
}
|
|
currentNode = saveCurrentNode;
|
|
}
|
|
|
|
function checkSourceFile(node: SourceFile) {
|
|
performance.mark("beforeCheck");
|
|
checkSourceFileWorker(node);
|
|
performance.mark("afterCheck");
|
|
performance.measure("Check", "beforeCheck", "afterCheck");
|
|
}
|
|
|
|
function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean {
|
|
if (isAmbient) {
|
|
return false;
|
|
}
|
|
switch (kind) {
|
|
case UnusedKind.Local:
|
|
return !!compilerOptions.noUnusedLocals;
|
|
case UnusedKind.Parameter:
|
|
return !!compilerOptions.noUnusedParameters;
|
|
default:
|
|
return Debug.assertNever(kind);
|
|
}
|
|
}
|
|
|
|
function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] {
|
|
return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray;
|
|
}
|
|
|
|
// Fully type check a source file and collect the relevant diagnostics.
|
|
function checkSourceFileWorker(node: SourceFile) {
|
|
const links = getNodeLinks(node);
|
|
if (!(links.flags & NodeCheckFlags.TypeChecked)) {
|
|
if (skipTypeChecking(node, compilerOptions, host)) {
|
|
return;
|
|
}
|
|
|
|
// Grammar checking
|
|
checkGrammarSourceFile(node);
|
|
|
|
clear(potentialThisCollisions);
|
|
clear(potentialNewTargetCollisions);
|
|
clear(potentialWeakMapCollisions);
|
|
|
|
forEach(node.statements, checkSourceElement);
|
|
checkSourceElement(node.endOfFileToken);
|
|
|
|
checkDeferredNodes(node);
|
|
|
|
if (isExternalOrCommonJsModule(node)) {
|
|
registerForUnusedIdentifiersCheck(node);
|
|
}
|
|
|
|
if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) {
|
|
checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => {
|
|
if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) {
|
|
diagnostics.add(diag);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error &&
|
|
!node.isDeclarationFile &&
|
|
isExternalModule(node)
|
|
) {
|
|
checkImportsForTypeOnlyConversion(node);
|
|
}
|
|
|
|
if (isExternalOrCommonJsModule(node)) {
|
|
checkExternalModuleExports(node);
|
|
}
|
|
|
|
if (potentialThisCollisions.length) {
|
|
forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope);
|
|
clear(potentialThisCollisions);
|
|
}
|
|
|
|
if (potentialNewTargetCollisions.length) {
|
|
forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope);
|
|
clear(potentialNewTargetCollisions);
|
|
}
|
|
|
|
if (potentialWeakMapCollisions.length) {
|
|
forEach(potentialWeakMapCollisions, checkWeakMapCollision);
|
|
clear(potentialWeakMapCollisions);
|
|
}
|
|
|
|
links.flags |= NodeCheckFlags.TypeChecked;
|
|
}
|
|
}
|
|
|
|
function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] {
|
|
try {
|
|
// Record the cancellation token so it can be checked later on during checkSourceElement.
|
|
// Do this in a finally block so we can ensure that it gets reset back to nothing after
|
|
// this call is done.
|
|
cancellationToken = ct;
|
|
return getDiagnosticsWorker(sourceFile);
|
|
}
|
|
finally {
|
|
cancellationToken = undefined;
|
|
}
|
|
}
|
|
|
|
function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] {
|
|
throwIfNonDiagnosticsProducing();
|
|
if (sourceFile) {
|
|
// Some global diagnostics are deferred until they are needed and
|
|
// may not be reported in the first call to getGlobalDiagnostics.
|
|
// We should catch these changes and report them.
|
|
const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
|
|
const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length;
|
|
|
|
checkSourceFile(sourceFile);
|
|
|
|
const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
|
|
const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
|
|
if (currentGlobalDiagnostics !== previousGlobalDiagnostics) {
|
|
// If the arrays are not the same reference, new diagnostics were added.
|
|
const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics);
|
|
return concatenate(deferredGlobalDiagnostics, semanticDiagnostics);
|
|
}
|
|
else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) {
|
|
// If the arrays are the same reference, but the length has changed, a single
|
|
// new diagnostic was added as DiagnosticCollection attempts to reuse the
|
|
// same array.
|
|
return concatenate(currentGlobalDiagnostics, semanticDiagnostics);
|
|
}
|
|
|
|
return semanticDiagnostics;
|
|
}
|
|
|
|
// Global diagnostics are always added when a file is not provided to
|
|
// getDiagnostics
|
|
forEach(host.getSourceFiles(), checkSourceFile);
|
|
return diagnostics.getDiagnostics();
|
|
}
|
|
|
|
function getGlobalDiagnostics(): Diagnostic[] {
|
|
throwIfNonDiagnosticsProducing();
|
|
return diagnostics.getGlobalDiagnostics();
|
|
}
|
|
|
|
function throwIfNonDiagnosticsProducing() {
|
|
if (!produceDiagnostics) {
|
|
throw new Error("Trying to get diagnostics from a type checker that does not produce them.");
|
|
}
|
|
}
|
|
|
|
// Language service support
|
|
|
|
function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] {
|
|
if (location.flags & NodeFlags.InWithStatement) {
|
|
// We cannot answer semantic questions within a with block, do not proceed any further
|
|
return [];
|
|
}
|
|
|
|
const symbols = createSymbolTable();
|
|
let isStatic = false;
|
|
|
|
populateSymbols();
|
|
|
|
symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword
|
|
return symbolsToArray(symbols);
|
|
|
|
function populateSymbols() {
|
|
while (location) {
|
|
if (location.locals && !isGlobalSourceFile(location)) {
|
|
copySymbols(location.locals, meaning);
|
|
}
|
|
|
|
switch (location.kind) {
|
|
case SyntaxKind.SourceFile:
|
|
if (!isExternalOrCommonJsModule(<SourceFile>location)) break;
|
|
// falls through
|
|
case SyntaxKind.ModuleDeclaration:
|
|
copySymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember);
|
|
break;
|
|
case SyntaxKind.EnumDeclaration:
|
|
copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember);
|
|
break;
|
|
case SyntaxKind.ClassExpression:
|
|
const className = (location as ClassExpression).name;
|
|
if (className) {
|
|
copySymbol(location.symbol, meaning);
|
|
}
|
|
|
|
// this fall-through is necessary because we would like to handle
|
|
// type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration.
|
|
// falls through
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
// If we didn't come from static member of class or interface,
|
|
// add the type parameters into the symbol table
|
|
// (type parameters of classDeclaration/classExpression and interface are in member property of the symbol.
|
|
// Note: that the memberFlags come from previous iteration.
|
|
if (!isStatic) {
|
|
copySymbols(getMembersOfSymbol(getSymbolOfNode(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type);
|
|
}
|
|
break;
|
|
case SyntaxKind.FunctionExpression:
|
|
const funcName = (location as FunctionExpression).name;
|
|
if (funcName) {
|
|
copySymbol(location.symbol, meaning);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (introducesArgumentsExoticObject(location)) {
|
|
copySymbol(argumentsSymbol, meaning);
|
|
}
|
|
|
|
isStatic = hasModifier(location, ModifierFlags.Static);
|
|
location = location.parent;
|
|
}
|
|
|
|
copySymbols(globals, meaning);
|
|
}
|
|
|
|
/**
|
|
* Copy the given symbol into symbol tables if the symbol has the given meaning
|
|
* and it doesn't already existed in the symbol table
|
|
* @param key a key for storing in symbol table; if undefined, use symbol.name
|
|
* @param symbol the symbol to be added into symbol table
|
|
* @param meaning meaning of symbol to filter by before adding to symbol table
|
|
*/
|
|
function copySymbol(symbol: Symbol, meaning: SymbolFlags): void {
|
|
if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) {
|
|
const id = symbol.escapedName;
|
|
// We will copy all symbol regardless of its reserved name because
|
|
// symbolsToArray will check whether the key is a reserved name and
|
|
// it will not copy symbol with reserved name to the array
|
|
if (!symbols.has(id)) {
|
|
symbols.set(id, symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
function copySymbols(source: SymbolTable, meaning: SymbolFlags): void {
|
|
if (meaning) {
|
|
source.forEach(symbol => {
|
|
copySymbol(symbol, meaning);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function isTypeDeclarationName(name: Node): boolean {
|
|
return name.kind === SyntaxKind.Identifier &&
|
|
isTypeDeclaration(name.parent) &&
|
|
name.parent.name === name;
|
|
}
|
|
|
|
function isTypeDeclaration(node: Node): node is TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumDeclaration | ImportClause | ImportSpecifier | ExportSpecifier {
|
|
switch (node.kind) {
|
|
case SyntaxKind.TypeParameter:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
return true;
|
|
case SyntaxKind.ImportClause:
|
|
return (node as ImportClause).isTypeOnly;
|
|
case SyntaxKind.ImportSpecifier:
|
|
case SyntaxKind.ExportSpecifier:
|
|
return (node as ImportSpecifier | ExportSpecifier).parent.parent.isTypeOnly;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// True if the given identifier is part of a type reference
|
|
function isTypeReferenceIdentifier(node: EntityName): boolean {
|
|
while (node.parent.kind === SyntaxKind.QualifiedName) {
|
|
node = node.parent as QualifiedName;
|
|
}
|
|
|
|
return node.parent.kind === SyntaxKind.TypeReference;
|
|
}
|
|
|
|
function isHeritageClauseElementIdentifier(node: Node): boolean {
|
|
while (node.parent.kind === SyntaxKind.PropertyAccessExpression) {
|
|
node = node.parent;
|
|
}
|
|
|
|
return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments;
|
|
}
|
|
|
|
function forEachEnclosingClass<T>(node: Node, callback: (node: Node) => T | undefined): T | undefined {
|
|
let result: T | undefined;
|
|
|
|
while (true) {
|
|
node = getContainingClass(node)!;
|
|
if (!node) break;
|
|
if (result = callback(node)) break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function isNodeUsedDuringClassInitialization(node: Node) {
|
|
return !!findAncestor(node, element => {
|
|
if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) {
|
|
return true;
|
|
}
|
|
else if (isClassLike(element) || isFunctionLikeDeclaration(element)) {
|
|
return "quit";
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) {
|
|
return !!forEachEnclosingClass(node, n => n === classDeclaration);
|
|
}
|
|
|
|
function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined {
|
|
while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) {
|
|
nodeOnRightSide = <QualifiedName>nodeOnRightSide.parent;
|
|
}
|
|
|
|
if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) {
|
|
return (<ImportEqualsDeclaration>nodeOnRightSide.parent).moduleReference === nodeOnRightSide ? <ImportEqualsDeclaration>nodeOnRightSide.parent : undefined;
|
|
}
|
|
|
|
if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) {
|
|
return (<ExportAssignment>nodeOnRightSide.parent).expression === <Node>nodeOnRightSide ? <ExportAssignment>nodeOnRightSide.parent : undefined;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function isInRightSideOfImportOrExportAssignment(node: EntityName) {
|
|
return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined;
|
|
}
|
|
|
|
function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) {
|
|
const specialPropertyAssignmentKind = getAssignmentDeclarationKind(entityName.parent.parent as BinaryExpression);
|
|
switch (specialPropertyAssignmentKind) {
|
|
case AssignmentDeclarationKind.ExportsProperty:
|
|
case AssignmentDeclarationKind.PrototypeProperty:
|
|
return getSymbolOfNode(entityName.parent);
|
|
case AssignmentDeclarationKind.ThisProperty:
|
|
case AssignmentDeclarationKind.ModuleExports:
|
|
case AssignmentDeclarationKind.Property:
|
|
return getSymbolOfNode(entityName.parent.parent);
|
|
}
|
|
}
|
|
|
|
function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined {
|
|
let parent = node.parent;
|
|
while (isQualifiedName(parent)) {
|
|
node = parent;
|
|
parent = parent.parent;
|
|
}
|
|
if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) {
|
|
return parent as ImportTypeNode;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression): Symbol | undefined {
|
|
if (isDeclarationName(name)) {
|
|
return getSymbolOfNode(name.parent);
|
|
}
|
|
|
|
if (isInJSFile(name) &&
|
|
name.parent.kind === SyntaxKind.PropertyAccessExpression &&
|
|
name.parent === (name.parent.parent as BinaryExpression).left) {
|
|
// Check if this is a special property assignment
|
|
if (!isPrivateIdentifier(name)) {
|
|
const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name);
|
|
if (specialPropertyAssignmentSymbol) {
|
|
return specialPropertyAssignmentSymbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (name.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(name)) {
|
|
// Even an entity name expression that doesn't resolve as an entityname may still typecheck as a property access expression
|
|
const success = resolveEntityName(name,
|
|
/*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*ignoreErrors*/ true);
|
|
if (success && success !== unknownSymbol) {
|
|
return success;
|
|
}
|
|
}
|
|
else if (!isPropertyAccessExpression(name) && !isPrivateIdentifier(name) && isInRightSideOfImportOrExportAssignment(name)) {
|
|
// Since we already checked for ExportAssignment, this really could only be an Import
|
|
const importEqualsDeclaration = getAncestor(name, SyntaxKind.ImportEqualsDeclaration);
|
|
Debug.assert(importEqualsDeclaration !== undefined);
|
|
return getSymbolOfPartOfRightHandSideOfImportEquals(name, /*dontResolveAlias*/ true);
|
|
}
|
|
|
|
if (!isPropertyAccessExpression(name) && !isPrivateIdentifier(name)) {
|
|
const possibleImportNode = isImportTypeQualifierPart(name);
|
|
if (possibleImportNode) {
|
|
getTypeFromTypeNode(possibleImportNode);
|
|
const sym = getNodeLinks(name).resolvedSymbol;
|
|
return sym === unknownSymbol ? undefined : sym;
|
|
}
|
|
}
|
|
|
|
while (isRightSideOfQualifiedNameOrPropertyAccess(name)) {
|
|
name = <QualifiedName | PropertyAccessEntityNameExpression>name.parent;
|
|
}
|
|
|
|
if (isHeritageClauseElementIdentifier(name)) {
|
|
let meaning = SymbolFlags.None;
|
|
// In an interface or class, we're definitely interested in a type.
|
|
if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) {
|
|
meaning = SymbolFlags.Type;
|
|
|
|
// In a class 'extends' clause we are also looking for a value.
|
|
if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) {
|
|
meaning |= SymbolFlags.Value;
|
|
}
|
|
}
|
|
else {
|
|
meaning = SymbolFlags.Namespace;
|
|
}
|
|
|
|
meaning |= SymbolFlags.Alias;
|
|
const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning) : undefined;
|
|
if (entityNameSymbol) {
|
|
return entityNameSymbol;
|
|
}
|
|
}
|
|
|
|
if (name.parent.kind === SyntaxKind.JSDocParameterTag) {
|
|
return getParameterSymbolFromJSDoc(name.parent as JSDocParameterTag);
|
|
}
|
|
|
|
if (name.parent.kind === SyntaxKind.TypeParameter && name.parent.parent.kind === SyntaxKind.JSDocTemplateTag) {
|
|
Debug.assert(!isInJSFile(name)); // Otherwise `isDeclarationName` would have been true.
|
|
const typeParameter = getTypeParameterFromJsDoc(name.parent as TypeParameterDeclaration & { parent: JSDocTemplateTag });
|
|
return typeParameter && typeParameter.symbol;
|
|
}
|
|
|
|
if (isExpressionNode(name)) {
|
|
if (nodeIsMissing(name)) {
|
|
// Missing entity name.
|
|
return undefined;
|
|
}
|
|
|
|
if (name.kind === SyntaxKind.Identifier) {
|
|
if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) {
|
|
const symbol = getIntrinsicTagSymbol(<JsxOpeningLikeElement>name.parent);
|
|
return symbol === unknownSymbol ? undefined : symbol;
|
|
}
|
|
|
|
return resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ false, /*dontResolveAlias*/ true);
|
|
}
|
|
else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) {
|
|
const links = getNodeLinks(name);
|
|
if (links.resolvedSymbol) {
|
|
return links.resolvedSymbol;
|
|
}
|
|
|
|
if (name.kind === SyntaxKind.PropertyAccessExpression) {
|
|
checkPropertyAccessExpression(name);
|
|
}
|
|
else {
|
|
checkQualifiedName(name);
|
|
}
|
|
return links.resolvedSymbol;
|
|
}
|
|
}
|
|
else if (isTypeReferenceIdentifier(<EntityName>name)) {
|
|
const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace;
|
|
return resolveEntityName(<EntityName>name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true);
|
|
}
|
|
|
|
if (name.parent.kind === SyntaxKind.TypePredicate) {
|
|
return resolveEntityName(<Identifier>name, /*meaning*/ SymbolFlags.FunctionScopedVariable);
|
|
}
|
|
|
|
// Do we want to return undefined here?
|
|
return undefined;
|
|
}
|
|
|
|
function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined {
|
|
if (node.kind === SyntaxKind.SourceFile) {
|
|
return isExternalModule(<SourceFile>node) ? getMergedSymbol(node.symbol) : undefined;
|
|
}
|
|
const { parent } = node;
|
|
const grandParent = parent.parent;
|
|
|
|
if (node.flags & NodeFlags.InWithStatement) {
|
|
// We cannot answer semantic questions within a with block, do not proceed any further
|
|
return undefined;
|
|
}
|
|
|
|
if (isDeclarationNameOrImportPropertyName(node)) {
|
|
// This is a declaration, call getSymbolOfNode
|
|
const parentSymbol = getSymbolOfNode(parent)!;
|
|
return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node
|
|
? getImmediateAliasedSymbol(parentSymbol)
|
|
: parentSymbol;
|
|
}
|
|
else if (isLiteralComputedPropertyDeclarationName(node)) {
|
|
return getSymbolOfNode(parent.parent);
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.Identifier) {
|
|
if (isInRightSideOfImportOrExportAssignment(<Identifier>node)) {
|
|
return getSymbolOfNameOrPropertyAccessExpression(<Identifier>node);
|
|
}
|
|
else if (parent.kind === SyntaxKind.BindingElement &&
|
|
grandParent.kind === SyntaxKind.ObjectBindingPattern &&
|
|
node === (<BindingElement>parent).propertyName) {
|
|
const typeOfPattern = getTypeOfNode(grandParent);
|
|
const propertyDeclaration = getPropertyOfType(typeOfPattern, (<Identifier>node).escapedText);
|
|
|
|
if (propertyDeclaration) {
|
|
return propertyDeclaration;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
case SyntaxKind.PrivateIdentifier:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.QualifiedName:
|
|
return getSymbolOfNameOrPropertyAccessExpression(<EntityName | PrivateIdentifier | PropertyAccessExpression>node);
|
|
|
|
case SyntaxKind.ThisKeyword:
|
|
const container = getThisContainer(node, /*includeArrowFunctions*/ false);
|
|
if (isFunctionLike(container)) {
|
|
const sig = getSignatureFromDeclaration(container);
|
|
if (sig.thisParameter) {
|
|
return sig.thisParameter;
|
|
}
|
|
}
|
|
if (isInExpressionContext(node)) {
|
|
return checkExpression(node as Expression).symbol;
|
|
}
|
|
// falls through
|
|
|
|
case SyntaxKind.ThisType:
|
|
return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol;
|
|
|
|
case SyntaxKind.SuperKeyword:
|
|
return checkExpression(node as Expression).symbol;
|
|
|
|
case SyntaxKind.ConstructorKeyword:
|
|
// constructor keyword for an overload, should take us to the definition if it exist
|
|
const constructorDeclaration = node.parent;
|
|
if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) {
|
|
return (<ClassDeclaration>constructorDeclaration.parent).symbol;
|
|
}
|
|
return undefined;
|
|
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
// 1). import x = require("./mo/*gotToDefinitionHere*/d")
|
|
// 2). External module name in an import declaration
|
|
// 3). Dynamic import call or require in javascript
|
|
// 4). type A = import("./f/*gotToDefinitionHere*/oo")
|
|
if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) ||
|
|
((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (<ImportDeclaration>node.parent).moduleSpecifier === node) ||
|
|
((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) ||
|
|
(isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent)
|
|
) {
|
|
return resolveExternalModuleName(node, <LiteralExpression>node, ignoreErrors);
|
|
}
|
|
if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) {
|
|
return getSymbolOfNode(parent);
|
|
}
|
|
// falls through
|
|
|
|
case SyntaxKind.NumericLiteral:
|
|
// index access
|
|
const objectType = isElementAccessExpression(parent)
|
|
? parent.argumentExpression === node ? getTypeOfExpression(parent.expression) : undefined
|
|
: isLiteralTypeNode(parent) && isIndexedAccessTypeNode(grandParent)
|
|
? getTypeFromTypeNode(grandParent.objectType)
|
|
: undefined;
|
|
return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text));
|
|
|
|
case SyntaxKind.DefaultKeyword:
|
|
case SyntaxKind.FunctionKeyword:
|
|
case SyntaxKind.EqualsGreaterThanToken:
|
|
case SyntaxKind.ClassKeyword:
|
|
return getSymbolOfNode(node.parent);
|
|
case SyntaxKind.ImportType:
|
|
return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal, ignoreErrors) : undefined;
|
|
|
|
case SyntaxKind.ExportKeyword:
|
|
return isExportAssignment(node.parent) ? Debug.checkDefined(node.parent.symbol) : undefined;
|
|
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined {
|
|
if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
return resolveEntityName((<ShorthandPropertyAssignment>location).name, SymbolFlags.Value | SymbolFlags.Alias);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/** Returns the target of an export specifier without following aliases */
|
|
function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier): Symbol | undefined {
|
|
return node.parent.parent.moduleSpecifier ?
|
|
getExternalModuleMember(node.parent.parent, node) :
|
|
resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
|
|
}
|
|
|
|
function getTypeOfNode(node: Node): Type {
|
|
if (node.flags & NodeFlags.InWithStatement) {
|
|
// We cannot answer semantic questions within a with block, do not proceed any further
|
|
return errorType;
|
|
}
|
|
|
|
const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node);
|
|
const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(classDecl.class));
|
|
if (isPartOfTypeNode(node)) {
|
|
const typeFromTypeNode = getTypeFromTypeNode(<TypeNode>node);
|
|
return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode;
|
|
}
|
|
|
|
if (isExpressionNode(node)) {
|
|
return getRegularTypeOfExpression(<Expression>node);
|
|
}
|
|
|
|
if (classType && !classDecl!.isImplements) {
|
|
// A SyntaxKind.ExpressionWithTypeArguments is considered a type node, except when it occurs in the
|
|
// extends clause of a class. We handle that case here.
|
|
const baseType = firstOrUndefined(getBaseTypes(classType));
|
|
return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType;
|
|
}
|
|
|
|
if (isTypeDeclaration(node)) {
|
|
// In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration
|
|
const symbol = getSymbolOfNode(node);
|
|
return getDeclaredTypeOfSymbol(symbol);
|
|
}
|
|
|
|
if (isTypeDeclarationName(node)) {
|
|
const symbol = getSymbolAtLocation(node);
|
|
return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType;
|
|
}
|
|
|
|
if (isDeclaration(node)) {
|
|
// In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration
|
|
const symbol = getSymbolOfNode(node);
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
|
|
if (isDeclarationNameOrImportPropertyName(node)) {
|
|
const symbol = getSymbolAtLocation(node);
|
|
if (symbol) {
|
|
return getTypeOfSymbol(symbol);
|
|
}
|
|
return errorType;
|
|
}
|
|
|
|
if (isBindingPattern(node)) {
|
|
return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true) || errorType;
|
|
}
|
|
|
|
if (isInRightSideOfImportOrExportAssignment(<Identifier>node)) {
|
|
const symbol = getSymbolAtLocation(node);
|
|
if (symbol) {
|
|
const declaredType = getDeclaredTypeOfSymbol(symbol);
|
|
return declaredType !== errorType ? declaredType : getTypeOfSymbol(symbol);
|
|
}
|
|
}
|
|
|
|
return errorType;
|
|
}
|
|
|
|
// Gets the type of object literal or array literal of destructuring assignment.
|
|
// { a } from
|
|
// for ( { a } of elems) {
|
|
// }
|
|
// [ a ] from
|
|
// [a] = [ some array ...]
|
|
function getTypeOfAssignmentPattern(expr: AssignmentPattern): Type | undefined {
|
|
Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression);
|
|
// If this is from "for of"
|
|
// for ( { a } of elems) {
|
|
// }
|
|
if (expr.parent.kind === SyntaxKind.ForOfStatement) {
|
|
const iteratedType = checkRightHandSideOfForOf((<ForOfStatement>expr.parent).expression, (<ForOfStatement>expr.parent).awaitModifier);
|
|
return checkDestructuringAssignment(expr, iteratedType || errorType);
|
|
}
|
|
// If this is from "for" initializer
|
|
// for ({a } = elems[0];.....) { }
|
|
if (expr.parent.kind === SyntaxKind.BinaryExpression) {
|
|
const iteratedType = getTypeOfExpression((<BinaryExpression>expr.parent).right);
|
|
return checkDestructuringAssignment(expr, iteratedType || errorType);
|
|
}
|
|
// If this is from nested object binding pattern
|
|
// for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) {
|
|
if (expr.parent.kind === SyntaxKind.PropertyAssignment) {
|
|
const node = cast(expr.parent.parent, isObjectLiteralExpression);
|
|
const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType;
|
|
const propertyIndex = indexOfNode(node.properties, expr.parent);
|
|
return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral, propertyIndex);
|
|
}
|
|
// Array literal assignment - array destructuring pattern
|
|
const node = cast(expr.parent, isArrayLiteralExpression);
|
|
// [{ property1: p1, property2 }] = elems;
|
|
const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType;
|
|
const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, typeOfArrayLiteral, undefinedType, expr.parent) || errorType;
|
|
return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType);
|
|
}
|
|
|
|
// Gets the property symbol corresponding to the property in destructuring assignment
|
|
// 'property1' from
|
|
// for ( { property1: a } of elems) {
|
|
// }
|
|
// 'property1' at location 'a' from:
|
|
// [a] = [ property1, property2 ]
|
|
function getPropertySymbolOfDestructuringAssignment(location: Identifier) {
|
|
// Get the type of the object or array literal and then look for property of given name in the type
|
|
const typeOfObjectLiteral = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern));
|
|
return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText);
|
|
}
|
|
|
|
function getRegularTypeOfExpression(expr: Expression): Type {
|
|
if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) {
|
|
expr = <Expression>expr.parent;
|
|
}
|
|
return getRegularTypeOfLiteralType(getTypeOfExpression(expr));
|
|
}
|
|
|
|
/**
|
|
* Gets either the static or instance type of a class element, based on
|
|
* whether the element is declared as "static".
|
|
*/
|
|
function getParentTypeOfClassElement(node: ClassElement) {
|
|
const classSymbol = getSymbolOfNode(node.parent)!;
|
|
return hasModifier(node, ModifierFlags.Static)
|
|
? getTypeOfSymbol(classSymbol)
|
|
: getDeclaredTypeOfSymbol(classSymbol);
|
|
}
|
|
|
|
function getClassElementPropertyKeyType(element: ClassElement) {
|
|
const name = element.name!;
|
|
switch (name.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return getLiteralType(idText(name));
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.StringLiteral:
|
|
return getLiteralType(name.text);
|
|
case SyntaxKind.ComputedPropertyName:
|
|
const nameType = checkComputedPropertyName(name);
|
|
return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType;
|
|
default:
|
|
return Debug.fail("Unsupported property name.");
|
|
}
|
|
}
|
|
|
|
// Return the list of properties of the given type, augmented with properties from Function
|
|
// if the type has call or construct signatures
|
|
function getAugmentedPropertiesOfType(type: Type): Symbol[] {
|
|
type = getApparentType(type);
|
|
const propsByName = createSymbolTable(getPropertiesOfType(type));
|
|
const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType :
|
|
getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType :
|
|
undefined;
|
|
if (functionType) {
|
|
forEach(getPropertiesOfType(functionType), p => {
|
|
if (!propsByName.has(p.escapedName)) {
|
|
propsByName.set(p.escapedName, p);
|
|
}
|
|
});
|
|
}
|
|
return getNamedMembers(propsByName);
|
|
}
|
|
|
|
function typeHasCallOrConstructSignatures(type: Type): boolean {
|
|
return ts.typeHasCallOrConstructSignatures(type, checker);
|
|
}
|
|
|
|
function getRootSymbols(symbol: Symbol): readonly Symbol[] {
|
|
const roots = getImmediateRootSymbols(symbol);
|
|
return roots ? flatMap(roots, getRootSymbols) : [symbol];
|
|
}
|
|
function getImmediateRootSymbols(symbol: Symbol): readonly Symbol[] | undefined {
|
|
if (getCheckFlags(symbol) & CheckFlags.Synthetic) {
|
|
return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName));
|
|
}
|
|
else if (symbol.flags & SymbolFlags.Transient) {
|
|
const { leftSpread, rightSpread, syntheticOrigin } = symbol as TransientSymbol;
|
|
return leftSpread ? [leftSpread, rightSpread!]
|
|
: syntheticOrigin ? [syntheticOrigin]
|
|
: singleElementArray(tryGetAliasTarget(symbol));
|
|
}
|
|
return undefined;
|
|
}
|
|
function tryGetAliasTarget(symbol: Symbol): Symbol | undefined {
|
|
let target: Symbol | undefined;
|
|
let next: Symbol | undefined = symbol;
|
|
while (next = getSymbolLinks(next).target) {
|
|
target = next;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
// Emitter support
|
|
|
|
function isArgumentsLocalBinding(nodeIn: Identifier): boolean {
|
|
if (!isGeneratedIdentifier(nodeIn)) {
|
|
const node = getParseTreeNode(nodeIn, isIdentifier);
|
|
if (node) {
|
|
const isPropertyName = node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node;
|
|
return !isPropertyName && getReferencedValueSymbol(node) === argumentsSymbol;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean {
|
|
let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression);
|
|
if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) {
|
|
// If the module is not found or is shorthand, assume that it may export a value.
|
|
return true;
|
|
}
|
|
|
|
const hasExportAssignment = hasExportAssignmentSymbol(moduleSymbol);
|
|
// if module has export assignment then 'resolveExternalModuleSymbol' will return resolved symbol for export assignment
|
|
// otherwise it will return moduleSymbol itself
|
|
moduleSymbol = resolveExternalModuleSymbol(moduleSymbol);
|
|
|
|
const symbolLinks = getSymbolLinks(moduleSymbol);
|
|
if (symbolLinks.exportsSomeValue === undefined) {
|
|
// for export assignments - check if resolved symbol for RHS is itself a value
|
|
// otherwise - check if at least one export is value
|
|
symbolLinks.exportsSomeValue = hasExportAssignment
|
|
? !!(moduleSymbol.flags & SymbolFlags.Value)
|
|
: forEachEntry(getExportsOfModule(moduleSymbol), isValue);
|
|
}
|
|
|
|
return symbolLinks.exportsSomeValue!;
|
|
|
|
function isValue(s: Symbol): boolean {
|
|
s = resolveSymbol(s);
|
|
return s && !!(s.flags & SymbolFlags.Value);
|
|
}
|
|
}
|
|
|
|
function isNameOfModuleOrEnumDeclaration(node: Identifier) {
|
|
return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name;
|
|
}
|
|
|
|
// When resolved as an expression identifier, if the given node references an exported entity, return the declaration
|
|
// node of the exported entity's container. Otherwise, return undefined.
|
|
function getReferencedExportContainer(nodeIn: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined {
|
|
const node = getParseTreeNode(nodeIn, isIdentifier);
|
|
if (node) {
|
|
// When resolving the export container for the name of a module or enum
|
|
// declaration, we need to start resolution at the declaration's container.
|
|
// Otherwise, we could incorrectly resolve the export container as the
|
|
// declaration if it contains an exported member with the same name.
|
|
let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node));
|
|
if (symbol) {
|
|
if (symbol.flags & SymbolFlags.ExportValue) {
|
|
// If we reference an exported entity within the same module declaration, then whether
|
|
// we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the
|
|
// kinds that we do NOT prefix.
|
|
const exportSymbol = getMergedSymbol(symbol.exportSymbol!);
|
|
if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal && !(exportSymbol.flags & SymbolFlags.Variable)) {
|
|
return undefined;
|
|
}
|
|
symbol = exportSymbol;
|
|
}
|
|
const parentSymbol = getParentOfSymbol(symbol);
|
|
if (parentSymbol) {
|
|
if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration.kind === SyntaxKind.SourceFile) {
|
|
const symbolFile = <SourceFile>parentSymbol.valueDeclaration;
|
|
const referenceFile = getSourceFileOfNode(node);
|
|
// If `node` accesses an export and that export isn't in the same file, then symbol is a namespace export, so return undefined.
|
|
const symbolIsUmdExport = symbolFile !== referenceFile;
|
|
return symbolIsUmdExport ? undefined : symbolFile;
|
|
}
|
|
return findAncestor(node.parent, (n): n is ModuleDeclaration | EnumDeclaration => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// When resolved as an expression identifier, if the given node references an import, return the declaration of
|
|
// that import. Otherwise, return undefined.
|
|
function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined {
|
|
const node = getParseTreeNode(nodeIn, isIdentifier);
|
|
if (node) {
|
|
const symbol = getReferencedValueSymbol(node);
|
|
// We should only get the declaration of an alias if there isn't a local value
|
|
// declaration for the symbol
|
|
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !getTypeOnlyAliasDeclaration(symbol)) {
|
|
return getDeclarationOfAliasSymbol(symbol);
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) {
|
|
return isBindingElement(symbol.valueDeclaration)
|
|
&& walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause;
|
|
}
|
|
|
|
function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean {
|
|
if (symbol.flags & SymbolFlags.BlockScoped && !isSourceFile(symbol.valueDeclaration)) {
|
|
const links = getSymbolLinks(symbol);
|
|
if (links.isDeclarationWithCollidingName === undefined) {
|
|
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
|
|
if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) {
|
|
const nodeLinks = getNodeLinks(symbol.valueDeclaration);
|
|
if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) {
|
|
// redeclaration - always should be renamed
|
|
links.isDeclarationWithCollidingName = true;
|
|
}
|
|
else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) {
|
|
// binding is captured in the function
|
|
// should be renamed if:
|
|
// - binding is not top level - top level bindings never collide with anything
|
|
// AND
|
|
// - binding is not declared in loop, should be renamed to avoid name reuse across siblings
|
|
// let a, b
|
|
// { let x = 1; a = () => x; }
|
|
// { let x = 100; b = () => x; }
|
|
// console.log(a()); // should print '1'
|
|
// console.log(b()); // should print '100'
|
|
// OR
|
|
// - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body
|
|
// * variables from initializer are passed to rewritten loop body as parameters so they are not captured directly
|
|
// * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus
|
|
// they will not collide with anything
|
|
const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop;
|
|
const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false);
|
|
const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false);
|
|
|
|
links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock));
|
|
}
|
|
else {
|
|
links.isDeclarationWithCollidingName = false;
|
|
}
|
|
}
|
|
}
|
|
return links.isDeclarationWithCollidingName!;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// When resolved as an expression identifier, if the given node references a nested block scoped entity with
|
|
// a name that either hides an existing name or might hide it when compiled downlevel,
|
|
// return the declaration of that entity. Otherwise, return undefined.
|
|
function getReferencedDeclarationWithCollidingName(nodeIn: Identifier): Declaration | undefined {
|
|
if (!isGeneratedIdentifier(nodeIn)) {
|
|
const node = getParseTreeNode(nodeIn, isIdentifier);
|
|
if (node) {
|
|
const symbol = getReferencedValueSymbol(node);
|
|
if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) {
|
|
return symbol.valueDeclaration;
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
// Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an
|
|
// existing name or might hide a name when compiled downlevel
|
|
function isDeclarationWithCollidingName(nodeIn: Declaration): boolean {
|
|
const node = getParseTreeNode(nodeIn, isDeclaration);
|
|
if (node) {
|
|
const symbol = getSymbolOfNode(node);
|
|
if (symbol) {
|
|
return isSymbolOfDeclarationWithCollidingName(symbol);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isValueAliasDeclaration(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
return isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol);
|
|
case SyntaxKind.ImportClause:
|
|
case SyntaxKind.NamespaceImport:
|
|
case SyntaxKind.ImportSpecifier:
|
|
case SyntaxKind.ExportSpecifier:
|
|
const symbol = getSymbolOfNode(node) || unknownSymbol;
|
|
return isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol);
|
|
case SyntaxKind.ExportDeclaration:
|
|
const exportClause = (<ExportDeclaration>node).exportClause;
|
|
return !!exportClause && (
|
|
isNamespaceExport(exportClause) ||
|
|
some(exportClause.elements, isValueAliasDeclaration)
|
|
);
|
|
case SyntaxKind.ExportAssignment:
|
|
return (<ExportAssignment>node).expression && (<ExportAssignment>node).expression.kind === SyntaxKind.Identifier ?
|
|
isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol) :
|
|
true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isTopLevelValueImportEqualsWithEntityName(nodeIn: ImportEqualsDeclaration): boolean {
|
|
const node = getParseTreeNode(nodeIn, isImportEqualsDeclaration);
|
|
if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) {
|
|
// parent is not source file or it is not reference to internal module
|
|
return false;
|
|
}
|
|
|
|
const isValue = isAliasResolvedToValue(getSymbolOfNode(node));
|
|
return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference);
|
|
}
|
|
|
|
function isAliasResolvedToValue(symbol: Symbol): boolean {
|
|
const target = resolveAlias(symbol);
|
|
if (target === unknownSymbol) {
|
|
return true;
|
|
}
|
|
// const enums and modules that contain only const enums are not considered values from the emit perspective
|
|
// unless 'preserveConstEnums' option is set to true
|
|
return !!(target.flags & SymbolFlags.Value) &&
|
|
(compilerOptions.preserveConstEnums || !isConstEnumOrConstEnumOnlyModule(target));
|
|
}
|
|
|
|
function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean {
|
|
return isConstEnumSymbol(s) || !!s.constEnumOnlyModule;
|
|
}
|
|
|
|
function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean {
|
|
if (isAliasSymbolDeclaration(node)) {
|
|
const symbol = getSymbolOfNode(node);
|
|
if (symbol && getSymbolLinks(symbol).referenced) {
|
|
return true;
|
|
}
|
|
const target = getSymbolLinks(symbol!).target; // TODO: GH#18217
|
|
if (target && getModifierFlags(node) & ModifierFlags.Export &&
|
|
target.flags & SymbolFlags.Value &&
|
|
(compilerOptions.preserveConstEnums || !isConstEnumOrConstEnumOnlyModule(target))) {
|
|
// An `export import ... =` of a value symbol is always considered referenced
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (checkChildren) {
|
|
return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isImplementationOfOverload(node: SignatureDeclaration) {
|
|
if (nodeIsPresent((node as FunctionLikeDeclaration).body)) {
|
|
if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures
|
|
const symbol = getSymbolOfNode(node);
|
|
const signaturesOfSymbol = getSignaturesOfSymbol(symbol);
|
|
// If this function body corresponds to function with multiple signature, it is implementation of overload
|
|
// e.g.: function foo(a: string): string;
|
|
// function foo(a: number): number;
|
|
// function foo(a: any) { // This is implementation of the overloads
|
|
// return a;
|
|
// }
|
|
return signaturesOfSymbol.length > 1 ||
|
|
// If there is single signature for the symbol, it is overload if that signature isn't coming from the node
|
|
// e.g.: function foo(a: string): string;
|
|
// function foo(a: any) { // This is implementation of the overloads
|
|
// return a;
|
|
// }
|
|
(signaturesOfSymbol.length === 1 && signaturesOfSymbol[0].declaration !== node);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean {
|
|
return !!strictNullChecks &&
|
|
!isOptionalParameter(parameter) &&
|
|
!isJSDocParameterTag(parameter) &&
|
|
!!parameter.initializer &&
|
|
!hasModifier(parameter, ModifierFlags.ParameterPropertyModifier);
|
|
}
|
|
|
|
function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) {
|
|
return strictNullChecks &&
|
|
isOptionalParameter(parameter) &&
|
|
!parameter.initializer &&
|
|
hasModifier(parameter, ModifierFlags.ParameterPropertyModifier);
|
|
}
|
|
|
|
function isExpandoFunctionDeclaration(node: Declaration): boolean {
|
|
const declaration = getParseTreeNode(node, isFunctionDeclaration);
|
|
if (!declaration) {
|
|
return false;
|
|
}
|
|
const symbol = getSymbolOfNode(declaration);
|
|
if (!symbol || !(symbol.flags & SymbolFlags.Function)) {
|
|
return false;
|
|
}
|
|
return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration));
|
|
}
|
|
|
|
function getPropertiesOfContainerFunction(node: Declaration): Symbol[] {
|
|
const declaration = getParseTreeNode(node, isFunctionDeclaration);
|
|
if (!declaration) {
|
|
return emptyArray;
|
|
}
|
|
const symbol = getSymbolOfNode(declaration);
|
|
return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray;
|
|
}
|
|
|
|
function getNodeCheckFlags(node: Node): NodeCheckFlags {
|
|
return getNodeLinks(node).flags || 0;
|
|
}
|
|
|
|
function getEnumMemberValue(node: EnumMember): string | number | undefined {
|
|
computeEnumMemberValues(node.parent);
|
|
return getNodeLinks(node).enumMemberValue;
|
|
}
|
|
|
|
function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression {
|
|
switch (node.kind) {
|
|
case SyntaxKind.EnumMember:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined {
|
|
if (node.kind === SyntaxKind.EnumMember) {
|
|
return getEnumMemberValue(node);
|
|
}
|
|
|
|
const symbol = getNodeLinks(node).resolvedSymbol;
|
|
if (symbol && (symbol.flags & SymbolFlags.EnumMember)) {
|
|
// inline property\index accesses only for const enums
|
|
const member = symbol.valueDeclaration as EnumMember;
|
|
if (isEnumConst(member.parent)) {
|
|
return getEnumMemberValue(member);
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function isFunctionType(type: Type): boolean {
|
|
return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0;
|
|
}
|
|
|
|
function getTypeReferenceSerializationKind(typeNameIn: EntityName, location?: Node): TypeReferenceSerializationKind {
|
|
// ensure both `typeName` and `location` are parse tree nodes.
|
|
const typeName = getParseTreeNode(typeNameIn, isEntityName);
|
|
if (!typeName) return TypeReferenceSerializationKind.Unknown;
|
|
|
|
if (location) {
|
|
location = getParseTreeNode(location);
|
|
if (!location) return TypeReferenceSerializationKind.Unknown;
|
|
}
|
|
|
|
// Resolve the symbol as a value to ensure the type can be reached at runtime during emit.
|
|
const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location);
|
|
|
|
// Resolve the symbol as a type so that we can provide a more useful hint for the type serializer.
|
|
const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location);
|
|
if (valueSymbol && valueSymbol === typeSymbol) {
|
|
const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false);
|
|
if (globalPromiseSymbol && valueSymbol === globalPromiseSymbol) {
|
|
return TypeReferenceSerializationKind.Promise;
|
|
}
|
|
|
|
const constructorType = getTypeOfSymbol(valueSymbol);
|
|
if (constructorType && isConstructorType(constructorType)) {
|
|
return TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue;
|
|
}
|
|
}
|
|
|
|
// We might not be able to resolve type symbol so use unknown type in that case (eg error case)
|
|
if (!typeSymbol) {
|
|
return TypeReferenceSerializationKind.Unknown;
|
|
}
|
|
const type = getDeclaredTypeOfSymbol(typeSymbol);
|
|
if (type === errorType) {
|
|
return TypeReferenceSerializationKind.Unknown;
|
|
}
|
|
else if (type.flags & TypeFlags.AnyOrUnknown) {
|
|
return TypeReferenceSerializationKind.ObjectType;
|
|
}
|
|
else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) {
|
|
return TypeReferenceSerializationKind.VoidNullableOrNeverType;
|
|
}
|
|
else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) {
|
|
return TypeReferenceSerializationKind.BooleanType;
|
|
}
|
|
else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) {
|
|
return TypeReferenceSerializationKind.NumberLikeType;
|
|
}
|
|
else if (isTypeAssignableToKind(type, TypeFlags.BigIntLike)) {
|
|
return TypeReferenceSerializationKind.BigIntLikeType;
|
|
}
|
|
else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) {
|
|
return TypeReferenceSerializationKind.StringLikeType;
|
|
}
|
|
else if (isTupleType(type)) {
|
|
return TypeReferenceSerializationKind.ArrayLikeType;
|
|
}
|
|
else if (isTypeAssignableToKind(type, TypeFlags.ESSymbolLike)) {
|
|
return TypeReferenceSerializationKind.ESSymbolType;
|
|
}
|
|
else if (isFunctionType(type)) {
|
|
return TypeReferenceSerializationKind.TypeWithCallSignature;
|
|
}
|
|
else if (isArrayType(type)) {
|
|
return TypeReferenceSerializationKind.ArrayLikeType;
|
|
}
|
|
else {
|
|
return TypeReferenceSerializationKind.ObjectType;
|
|
}
|
|
}
|
|
|
|
function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) {
|
|
const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor);
|
|
if (!declaration) {
|
|
return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode;
|
|
}
|
|
// Get type of the symbol if this is the valid symbol otherwise get type at location
|
|
const symbol = getSymbolOfNode(declaration);
|
|
let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature))
|
|
? getWidenedLiteralType(getTypeOfSymbol(symbol))
|
|
: errorType;
|
|
if (type.flags & TypeFlags.UniqueESSymbol &&
|
|
type.symbol === symbol) {
|
|
flags |= NodeBuilderFlags.AllowUniqueESSymbolType;
|
|
}
|
|
if (addUndefined) {
|
|
type = getOptionalType(type);
|
|
}
|
|
return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker);
|
|
}
|
|
|
|
function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) {
|
|
const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike);
|
|
if (!signatureDeclaration) {
|
|
return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode;
|
|
}
|
|
const signature = getSignatureFromDeclaration(signatureDeclaration);
|
|
return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker);
|
|
}
|
|
|
|
function createTypeOfExpression(exprIn: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) {
|
|
const expr = getParseTreeNode(exprIn, isExpression);
|
|
if (!expr) {
|
|
return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode;
|
|
}
|
|
const type = getWidenedType(getRegularTypeOfExpression(expr));
|
|
return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker);
|
|
}
|
|
|
|
function hasGlobalName(name: string): boolean {
|
|
return globals.has(escapeLeadingUnderscores(name));
|
|
}
|
|
|
|
function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined {
|
|
const resolvedSymbol = getNodeLinks(reference).resolvedSymbol;
|
|
if (resolvedSymbol) {
|
|
return resolvedSymbol;
|
|
}
|
|
|
|
let location: Node = reference;
|
|
if (startInDeclarationContainer) {
|
|
// When resolving the name of a declaration as a value, we need to start resolution
|
|
// at a point outside of the declaration.
|
|
const parent = reference.parent;
|
|
if (isDeclaration(parent) && reference === parent.name) {
|
|
location = getDeclarationContainer(parent);
|
|
}
|
|
}
|
|
|
|
return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
|
|
}
|
|
|
|
function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined {
|
|
if (!isGeneratedIdentifier(referenceIn)) {
|
|
const reference = getParseTreeNode(referenceIn, isIdentifier);
|
|
if (reference) {
|
|
const symbol = getReferencedValueSymbol(reference);
|
|
if (symbol) {
|
|
return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration;
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean {
|
|
if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) {
|
|
return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node)));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression {
|
|
const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker)
|
|
: type === trueType ? createTrue() : type === falseType && createFalse();
|
|
return enumResult || createLiteral((type as LiteralType).value);
|
|
}
|
|
|
|
function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) {
|
|
const type = getTypeOfSymbol(getSymbolOfNode(node));
|
|
return literalTypeToNode(<FreshableType>type, node, tracker);
|
|
}
|
|
|
|
function getJsxFactoryEntity(location: Node) {
|
|
return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity;
|
|
}
|
|
|
|
function createResolver(): EmitResolver {
|
|
// this variable and functions that use it are deliberately moved here from the outer scope
|
|
// to avoid scope pollution
|
|
const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives();
|
|
let fileToDirective: Map<string>;
|
|
if (resolvedTypeReferenceDirectives) {
|
|
// populate reverse mapping: file path -> type reference directive that was resolved to this file
|
|
fileToDirective = createMap<string>();
|
|
resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => {
|
|
if (!resolvedDirective || !resolvedDirective.resolvedFileName) {
|
|
return;
|
|
}
|
|
const file = host.getSourceFile(resolvedDirective.resolvedFileName)!;
|
|
// Add the transitive closure of path references loaded by this file (as long as they are not)
|
|
// part of an existing type reference.
|
|
addReferencedFilesToTypeDirective(file, key);
|
|
});
|
|
}
|
|
|
|
return {
|
|
getReferencedExportContainer,
|
|
getReferencedImportDeclaration,
|
|
getReferencedDeclarationWithCollidingName,
|
|
isDeclarationWithCollidingName,
|
|
isValueAliasDeclaration: node => {
|
|
node = getParseTreeNode(node);
|
|
// Synthesized nodes are always treated like values.
|
|
return node ? isValueAliasDeclaration(node) : true;
|
|
},
|
|
hasGlobalName,
|
|
isReferencedAliasDeclaration: (node, checkChildren?) => {
|
|
node = getParseTreeNode(node);
|
|
// Synthesized nodes are always treated as referenced.
|
|
return node ? isReferencedAliasDeclaration(node, checkChildren) : true;
|
|
},
|
|
getNodeCheckFlags: node => {
|
|
node = getParseTreeNode(node);
|
|
return node ? getNodeCheckFlags(node) : 0;
|
|
},
|
|
isTopLevelValueImportEqualsWithEntityName,
|
|
isDeclarationVisible,
|
|
isImplementationOfOverload,
|
|
isRequiredInitializedParameter,
|
|
isOptionalUninitializedParameterProperty,
|
|
isExpandoFunctionDeclaration,
|
|
getPropertiesOfContainerFunction,
|
|
createTypeOfDeclaration,
|
|
createReturnTypeOfSignatureDeclaration,
|
|
createTypeOfExpression,
|
|
createLiteralConstValue,
|
|
isSymbolAccessible,
|
|
isEntityNameVisible,
|
|
getConstantValue: nodeIn => {
|
|
const node = getParseTreeNode(nodeIn, canHaveConstantValue);
|
|
return node ? getConstantValue(node) : undefined;
|
|
},
|
|
collectLinkedAliases,
|
|
getReferencedValueDeclaration,
|
|
getTypeReferenceSerializationKind,
|
|
isOptionalParameter,
|
|
moduleExportsSomeValue,
|
|
isArgumentsLocalBinding,
|
|
getExternalModuleFileFromDeclaration,
|
|
getTypeReferenceDirectivesForEntityName,
|
|
getTypeReferenceDirectivesForSymbol,
|
|
isLiteralConstDeclaration,
|
|
isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => {
|
|
const node = getParseTreeNode(nodeIn, isDeclaration);
|
|
const symbol = node && getSymbolOfNode(node);
|
|
return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late);
|
|
},
|
|
getJsxFactoryEntity,
|
|
getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations {
|
|
accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217
|
|
const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor;
|
|
const otherAccessor = getDeclarationOfKind<AccessorDeclaration>(getSymbolOfNode(accessor), otherKind);
|
|
const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor;
|
|
const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor;
|
|
const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration;
|
|
const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration;
|
|
return {
|
|
firstAccessor,
|
|
secondAccessor,
|
|
setAccessor,
|
|
getAccessor
|
|
};
|
|
},
|
|
getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined),
|
|
isBindingCapturedByNode: (node, decl) => {
|
|
const parseNode = getParseTreeNode(node);
|
|
const parseDecl = getParseTreeNode(decl);
|
|
return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl);
|
|
},
|
|
getDeclarationStatementsForSourceFile: (node, flags, tracker, bundled) => {
|
|
const n = getParseTreeNode(node) as SourceFile;
|
|
Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile");
|
|
const sym = getSymbolOfNode(node);
|
|
if (!sym) {
|
|
return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled);
|
|
}
|
|
return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled);
|
|
}
|
|
};
|
|
|
|
function isInHeritageClause(node: PropertyAccessEntityNameExpression) {
|
|
return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause;
|
|
}
|
|
|
|
// defined here to avoid outer scope pollution
|
|
function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] | undefined {
|
|
// program does not have any files with type reference directives - bail out
|
|
if (!fileToDirective) {
|
|
return undefined;
|
|
}
|
|
// property access can only be used as values, or types when within an expression with type arguments inside a heritage clause
|
|
// qualified names can only be used as types\namespaces
|
|
// identifiers are treated as values only if they appear in type queries
|
|
let meaning = SymbolFlags.Type | SymbolFlags.Namespace;
|
|
if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) {
|
|
meaning = SymbolFlags.Value | SymbolFlags.ExportValue;
|
|
}
|
|
|
|
const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true);
|
|
return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined;
|
|
}
|
|
|
|
// defined here to avoid outer scope pollution
|
|
function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[] | undefined {
|
|
// program does not have any files with type reference directives - bail out
|
|
if (!fileToDirective) {
|
|
return undefined;
|
|
}
|
|
if (!isSymbolFromTypeDeclarationFile(symbol)) {
|
|
return undefined;
|
|
}
|
|
// check what declarations in the symbol can contribute to the target meaning
|
|
let typeReferenceDirectives: string[] | undefined;
|
|
for (const decl of symbol.declarations) {
|
|
// check meaning of the local symbol to see if declaration needs to be analyzed further
|
|
if (decl.symbol && decl.symbol.flags & meaning!) {
|
|
const file = getSourceFileOfNode(decl);
|
|
const typeReferenceDirective = fileToDirective.get(file.path);
|
|
if (typeReferenceDirective) {
|
|
(typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective);
|
|
}
|
|
else {
|
|
// found at least one entry that does not originate from type reference directive
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|
|
return typeReferenceDirectives;
|
|
}
|
|
|
|
function isSymbolFromTypeDeclarationFile(symbol: Symbol): boolean {
|
|
// bail out if symbol does not have associated declarations (i.e. this is transient symbol created for property in binding pattern)
|
|
if (!symbol.declarations) {
|
|
return false;
|
|
}
|
|
|
|
// walk the parent chain for symbols to make sure that top level parent symbol is in the global scope
|
|
// external modules cannot define or contribute to type declaration files
|
|
let current = symbol;
|
|
while (true) {
|
|
const parent = getParentOfSymbol(current);
|
|
if (parent) {
|
|
current = parent;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) {
|
|
return false;
|
|
}
|
|
|
|
// check that at least one declaration of top level symbol originates from type declaration file
|
|
for (const decl of symbol.declarations) {
|
|
const file = getSourceFileOfNode(decl);
|
|
if (fileToDirective.has(file.path)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function addReferencedFilesToTypeDirective(file: SourceFile, key: string) {
|
|
if (fileToDirective.has(file.path)) return;
|
|
fileToDirective.set(file.path, key);
|
|
for (const { fileName } of file.referencedFiles) {
|
|
const resolvedFile = resolveTripleslashReference(fileName, file.originalFileName);
|
|
const referencedFile = host.getSourceFile(resolvedFile);
|
|
if (referencedFile) {
|
|
addReferencedFilesToTypeDirective(referencedFile, key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode): SourceFile | undefined {
|
|
const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration);
|
|
const moduleSymbol = resolveExternalModuleNameWorker(specifier!, specifier!, /*moduleNotFoundError*/ undefined); // TODO: GH#18217
|
|
if (!moduleSymbol) {
|
|
return undefined;
|
|
}
|
|
return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile);
|
|
}
|
|
|
|
function initializeTypeChecker() {
|
|
// Bind all source files and propagate errors
|
|
for (const file of host.getSourceFiles()) {
|
|
bindSourceFile(file, compilerOptions);
|
|
}
|
|
|
|
amalgamatedDuplicates = createMap();
|
|
|
|
// Initialize global symbol table
|
|
let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined;
|
|
for (const file of host.getSourceFiles()) {
|
|
if (file.redirectInfo) {
|
|
continue;
|
|
}
|
|
if (!isExternalOrCommonJsModule(file)) {
|
|
// It is an error for a non-external-module (i.e. script) to declare its own `globalThis`.
|
|
// We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files.
|
|
const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String);
|
|
if (fileGlobalThisSymbol) {
|
|
for (const declaration of fileGlobalThisSymbol.declarations) {
|
|
diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis"));
|
|
}
|
|
}
|
|
mergeSymbolTable(globals, file.locals!);
|
|
}
|
|
if (file.jsGlobalAugmentations) {
|
|
mergeSymbolTable(globals, file.jsGlobalAugmentations);
|
|
}
|
|
if (file.patternAmbientModules && file.patternAmbientModules.length) {
|
|
patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules);
|
|
}
|
|
if (file.moduleAugmentations.length) {
|
|
(augmentations || (augmentations = [])).push(file.moduleAugmentations);
|
|
}
|
|
if (file.symbol && file.symbol.globalExports) {
|
|
// Merge in UMD exports with first-in-wins semantics (see #9771)
|
|
const source = file.symbol.globalExports;
|
|
source.forEach((sourceSymbol, id) => {
|
|
if (!globals.has(id)) {
|
|
globals.set(id, sourceSymbol);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// We do global augmentations separately from module augmentations (and before creating global types) because they
|
|
// 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also,
|
|
// 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require
|
|
// checking for an export or property on the module (if export=) which, in turn, can fall back to the
|
|
// apparent type of the module - either globalObjectType or globalFunctionType - which wouldn't exist if we
|
|
// did module augmentations prior to finalizing the global types.
|
|
if (augmentations) {
|
|
// merge _global_ module augmentations.
|
|
// this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed
|
|
for (const list of augmentations) {
|
|
for (const augmentation of list) {
|
|
if (!isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue;
|
|
mergeModuleAugmentation(augmentation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Setup global builtins
|
|
addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0);
|
|
|
|
getSymbolLinks(undefinedSymbol).type = undefinedWideningType;
|
|
getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true);
|
|
getSymbolLinks(unknownSymbol).type = errorType;
|
|
getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol);
|
|
|
|
// Initialize special types
|
|
globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true);
|
|
globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true);
|
|
globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true);
|
|
globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType;
|
|
globalNewableFunctionType = strictBindCallApply && getGlobalType("NewableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType;
|
|
globalStringType = getGlobalType("String" as __String, /*arity*/ 0, /*reportErrors*/ true);
|
|
globalNumberType = getGlobalType("Number" as __String, /*arity*/ 0, /*reportErrors*/ true);
|
|
globalBooleanType = getGlobalType("Boolean" as __String, /*arity*/ 0, /*reportErrors*/ true);
|
|
globalRegExpType = getGlobalType("RegExp" as __String, /*arity*/ 0, /*reportErrors*/ true);
|
|
anyArrayType = createArrayType(anyType);
|
|
|
|
autoArrayType = createArrayType(autoType);
|
|
if (autoArrayType === emptyObjectType) {
|
|
// autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type
|
|
autoArrayType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
|
}
|
|
|
|
globalReadonlyArrayType = <GenericType>getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) || globalArrayType;
|
|
anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType;
|
|
globalThisType = <GenericType>getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1);
|
|
|
|
if (augmentations) {
|
|
// merge _nonglobal_ module augmentations.
|
|
// this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed
|
|
for (const list of augmentations) {
|
|
for (const augmentation of list) {
|
|
if (isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue;
|
|
mergeModuleAugmentation(augmentation);
|
|
}
|
|
}
|
|
}
|
|
|
|
amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => {
|
|
// If not many things conflict, issue individual errors
|
|
if (conflictingSymbols.size < 8) {
|
|
conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => {
|
|
const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0;
|
|
for (const node of firstFileLocations) {
|
|
addDuplicateDeclarationError(node, message, symbolName, secondFileLocations);
|
|
}
|
|
for (const node of secondFileLocations) {
|
|
addDuplicateDeclarationError(node, message, symbolName, firstFileLocations);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
// Otherwise issue top-level error since the files appear very identical in terms of what they contain
|
|
const list = arrayFrom(conflictingSymbols.keys()).join(", ");
|
|
diagnostics.add(addRelatedInfo(
|
|
createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
|
|
createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file)
|
|
));
|
|
diagnostics.add(addRelatedInfo(
|
|
createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
|
|
createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file)
|
|
));
|
|
}
|
|
});
|
|
amalgamatedDuplicates = undefined;
|
|
}
|
|
|
|
function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) {
|
|
if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) {
|
|
const sourceFile = getSourceFileOfNode(location);
|
|
if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) {
|
|
const helpersModule = resolveHelpersModule(sourceFile, location);
|
|
if (helpersModule !== unknownSymbol) {
|
|
const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers;
|
|
for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) {
|
|
if (uncheckedHelpers & helper) {
|
|
const name = getHelperName(helper);
|
|
const symbol = getSymbol(helpersModule.exports!, escapeLeadingUnderscores(name), SymbolFlags.Value);
|
|
if (!symbol) {
|
|
error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_which_does_not_exist_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
requestedExternalEmitHelpers |= helpers;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getHelperName(helper: ExternalEmitHelpers) {
|
|
switch (helper) {
|
|
case ExternalEmitHelpers.Extends: return "__extends";
|
|
case ExternalEmitHelpers.Assign: return "__assign";
|
|
case ExternalEmitHelpers.Rest: return "__rest";
|
|
case ExternalEmitHelpers.Decorate: return "__decorate";
|
|
case ExternalEmitHelpers.Metadata: return "__metadata";
|
|
case ExternalEmitHelpers.Param: return "__param";
|
|
case ExternalEmitHelpers.Awaiter: return "__awaiter";
|
|
case ExternalEmitHelpers.Generator: return "__generator";
|
|
case ExternalEmitHelpers.Values: return "__values";
|
|
case ExternalEmitHelpers.Read: return "__read";
|
|
case ExternalEmitHelpers.Spread: return "__spread";
|
|
case ExternalEmitHelpers.SpreadArrays: return "__spreadArrays";
|
|
case ExternalEmitHelpers.Await: return "__await";
|
|
case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator";
|
|
case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator";
|
|
case ExternalEmitHelpers.AsyncValues: return "__asyncValues";
|
|
case ExternalEmitHelpers.ExportStar: return "__exportStar";
|
|
case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject";
|
|
case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet";
|
|
case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet";
|
|
case ExternalEmitHelpers.CreateBinding: return "__createBinding";
|
|
default: return Debug.fail("Unrecognized helper");
|
|
}
|
|
}
|
|
|
|
function resolveHelpersModule(node: SourceFile, errorNode: Node) {
|
|
if (!externalHelpersModule) {
|
|
externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol;
|
|
}
|
|
return externalHelpersModule;
|
|
}
|
|
|
|
// GRAMMAR CHECKING
|
|
function checkGrammarDecoratorsAndModifiers(node: Node): boolean {
|
|
return checkGrammarDecorators(node) || checkGrammarModifiers(node);
|
|
}
|
|
|
|
function checkGrammarDecorators(node: Node): boolean {
|
|
if (!node.decorators) {
|
|
return false;
|
|
}
|
|
if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) {
|
|
if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent((<MethodDeclaration>node).body)) {
|
|
return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload);
|
|
}
|
|
else {
|
|
return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here);
|
|
}
|
|
}
|
|
else if (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) {
|
|
const accessors = getAllAccessorDeclarations((<ClassDeclaration>node.parent).members, <AccessorDeclaration>node);
|
|
if (accessors.firstAccessor.decorators && node === accessors.secondAccessor) {
|
|
return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarModifiers(node: Node): boolean {
|
|
const quickResult = reportObviousModifierErrors(node);
|
|
if (quickResult !== undefined) {
|
|
return quickResult;
|
|
}
|
|
|
|
let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined;
|
|
let flags = ModifierFlags.None;
|
|
for (const modifier of node.modifiers!) {
|
|
if (modifier.kind !== SyntaxKind.ReadonlyKeyword) {
|
|
if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind));
|
|
}
|
|
if (node.kind === SyntaxKind.IndexSignature) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind));
|
|
}
|
|
}
|
|
switch (modifier.kind) {
|
|
case SyntaxKind.ConstKeyword:
|
|
if (node.kind !== SyntaxKind.EnumDeclaration) {
|
|
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword));
|
|
}
|
|
break;
|
|
case SyntaxKind.PublicKeyword:
|
|
case SyntaxKind.ProtectedKeyword:
|
|
case SyntaxKind.PrivateKeyword:
|
|
const text = visibilityToString(modifierToFlag(modifier.kind));
|
|
|
|
if (flags & ModifierFlags.AccessibilityModifier) {
|
|
return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen);
|
|
}
|
|
else if (flags & ModifierFlags.Static) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static");
|
|
}
|
|
else if (flags & ModifierFlags.Readonly) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly");
|
|
}
|
|
else if (flags & ModifierFlags.Async) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async");
|
|
}
|
|
else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text);
|
|
}
|
|
else if (flags & ModifierFlags.Abstract) {
|
|
if (modifier.kind === SyntaxKind.PrivateKeyword) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract");
|
|
}
|
|
else {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "abstract");
|
|
}
|
|
}
|
|
else if (isPrivateIdentifierPropertyDeclaration(node)) {
|
|
return grammarErrorOnNode(modifier, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier);
|
|
}
|
|
flags |= modifierToFlag(modifier.kind);
|
|
break;
|
|
|
|
case SyntaxKind.StaticKeyword:
|
|
if (flags & ModifierFlags.Static) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static");
|
|
}
|
|
else if (flags & ModifierFlags.Readonly) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly");
|
|
}
|
|
else if (flags & ModifierFlags.Async) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async");
|
|
}
|
|
else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, "static");
|
|
}
|
|
else if (node.kind === SyntaxKind.Parameter) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static");
|
|
}
|
|
else if (flags & ModifierFlags.Abstract) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract");
|
|
}
|
|
else if (isPrivateIdentifierPropertyDeclaration(node)) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "static");
|
|
}
|
|
flags |= ModifierFlags.Static;
|
|
lastStatic = modifier;
|
|
break;
|
|
|
|
case SyntaxKind.ReadonlyKeyword:
|
|
if (flags & ModifierFlags.Readonly) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly");
|
|
}
|
|
else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) {
|
|
// If node.kind === SyntaxKind.Parameter, checkParameter report an error if it's not a parameter property.
|
|
return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature);
|
|
}
|
|
flags |= ModifierFlags.Readonly;
|
|
lastReadonly = modifier;
|
|
break;
|
|
|
|
case SyntaxKind.ExportKeyword:
|
|
if (flags & ModifierFlags.Export) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export");
|
|
}
|
|
else if (flags & ModifierFlags.Ambient) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare");
|
|
}
|
|
else if (flags & ModifierFlags.Abstract) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract");
|
|
}
|
|
else if (flags & ModifierFlags.Async) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async");
|
|
}
|
|
else if (isClassLike(node.parent)) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_class_element, "export");
|
|
}
|
|
else if (node.kind === SyntaxKind.Parameter) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export");
|
|
}
|
|
flags |= ModifierFlags.Export;
|
|
break;
|
|
case SyntaxKind.DefaultKeyword:
|
|
const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent;
|
|
if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) {
|
|
return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module);
|
|
}
|
|
|
|
flags |= ModifierFlags.Default;
|
|
break;
|
|
case SyntaxKind.DeclareKeyword:
|
|
if (flags & ModifierFlags.Ambient) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare");
|
|
}
|
|
else if (flags & ModifierFlags.Async) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async");
|
|
}
|
|
else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_class_element, "declare");
|
|
}
|
|
else if (node.kind === SyntaxKind.Parameter) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "declare");
|
|
}
|
|
else if ((node.parent.flags & NodeFlags.Ambient) && node.parent.kind === SyntaxKind.ModuleBlock) {
|
|
return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context);
|
|
}
|
|
else if (isPrivateIdentifierPropertyDeclaration(node)) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "declare");
|
|
}
|
|
flags |= ModifierFlags.Ambient;
|
|
lastDeclare = modifier;
|
|
break;
|
|
|
|
case SyntaxKind.AbstractKeyword:
|
|
if (flags & ModifierFlags.Abstract) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract");
|
|
}
|
|
if (node.kind !== SyntaxKind.ClassDeclaration) {
|
|
if (node.kind !== SyntaxKind.MethodDeclaration &&
|
|
node.kind !== SyntaxKind.PropertyDeclaration &&
|
|
node.kind !== SyntaxKind.GetAccessor &&
|
|
node.kind !== SyntaxKind.SetAccessor) {
|
|
return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration);
|
|
}
|
|
if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasModifier(node.parent, ModifierFlags.Abstract))) {
|
|
return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class);
|
|
}
|
|
if (flags & ModifierFlags.Static) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract");
|
|
}
|
|
if (flags & ModifierFlags.Private) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract");
|
|
}
|
|
}
|
|
if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.PrivateIdentifier) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "abstract");
|
|
}
|
|
|
|
flags |= ModifierFlags.Abstract;
|
|
break;
|
|
|
|
case SyntaxKind.AsyncKeyword:
|
|
if (flags & ModifierFlags.Async) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async");
|
|
}
|
|
else if (flags & ModifierFlags.Ambient || node.parent.flags & NodeFlags.Ambient) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async");
|
|
}
|
|
else if (node.kind === SyntaxKind.Parameter) {
|
|
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async");
|
|
}
|
|
flags |= ModifierFlags.Async;
|
|
lastAsync = modifier;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.Constructor) {
|
|
if (flags & ModifierFlags.Static) {
|
|
return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static");
|
|
}
|
|
if (flags & ModifierFlags.Abstract) {
|
|
return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217
|
|
}
|
|
else if (flags & ModifierFlags.Async) {
|
|
return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async");
|
|
}
|
|
else if (flags & ModifierFlags.Readonly) {
|
|
return grammarErrorOnNode(lastReadonly!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly");
|
|
}
|
|
return false;
|
|
}
|
|
else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) {
|
|
return grammarErrorOnNode(lastDeclare!, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare");
|
|
}
|
|
else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && isBindingPattern((<ParameterDeclaration>node).name)) {
|
|
return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_declared_using_a_binding_pattern);
|
|
}
|
|
else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && (<ParameterDeclaration>node).dotDotDotToken) {
|
|
return grammarErrorOnNode(node, Diagnostics.A_parameter_property_cannot_be_declared_using_a_rest_parameter);
|
|
}
|
|
if (flags & ModifierFlags.Async) {
|
|
return checkGrammarAsyncModifier(node, lastAsync!);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* true | false: Early return this value from checkGrammarModifiers.
|
|
* undefined: Need to do full checking on the modifiers.
|
|
*/
|
|
function reportObviousModifierErrors(node: Node): boolean | undefined {
|
|
return !node.modifiers
|
|
? false
|
|
: shouldReportBadModifier(node)
|
|
? grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here)
|
|
: undefined;
|
|
}
|
|
function shouldReportBadModifier(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.IndexSignature:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.ImportDeclaration:
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
case SyntaxKind.ExportDeclaration:
|
|
case SyntaxKind.ExportAssignment:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.Parameter:
|
|
return false;
|
|
default:
|
|
if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) {
|
|
return false;
|
|
}
|
|
switch (node.kind) {
|
|
case SyntaxKind.FunctionDeclaration:
|
|
return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword);
|
|
case SyntaxKind.ClassDeclaration:
|
|
return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword);
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.VariableStatement:
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
return true;
|
|
case SyntaxKind.EnumDeclaration:
|
|
return nodeHasAnyModifiersExcept(node, SyntaxKind.ConstKeyword);
|
|
default:
|
|
Debug.fail();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
function nodeHasAnyModifiersExcept(node: Node, allowedModifier: SyntaxKind): boolean {
|
|
return node.modifiers!.length > 1 || node.modifiers![0].kind !== allowedModifier;
|
|
}
|
|
|
|
function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
return false;
|
|
}
|
|
|
|
return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async");
|
|
}
|
|
|
|
function checkGrammarForDisallowedTrailingComma(list: NodeArray<Node> | undefined, diag = Diagnostics.Trailing_comma_not_allowed): boolean {
|
|
if (list && list.hasTrailingComma) {
|
|
return grammarErrorAtPos(list[0], list.end - ",".length, ",".length, diag);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarTypeParameterList(typeParameters: NodeArray<TypeParameterDeclaration> | undefined, file: SourceFile): boolean {
|
|
if (typeParameters && typeParameters.length === 0) {
|
|
const start = typeParameters.pos - "<".length;
|
|
const end = skipTrivia(file.text, typeParameters.end) + ">".length;
|
|
return grammarErrorAtPos(file, start, end - start, Diagnostics.Type_parameter_list_cannot_be_empty);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarParameterList(parameters: NodeArray<ParameterDeclaration>) {
|
|
let seenOptionalParameter = false;
|
|
const parameterCount = parameters.length;
|
|
|
|
for (let i = 0; i < parameterCount; i++) {
|
|
const parameter = parameters[i];
|
|
if (parameter.dotDotDotToken) {
|
|
if (i !== (parameterCount - 1)) {
|
|
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list);
|
|
}
|
|
if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070
|
|
checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
|
|
}
|
|
|
|
if (parameter.questionToken) {
|
|
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional);
|
|
}
|
|
|
|
if (parameter.initializer) {
|
|
return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer);
|
|
}
|
|
}
|
|
else if (parameter.questionToken) {
|
|
seenOptionalParameter = true;
|
|
|
|
if (parameter.initializer) {
|
|
return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer);
|
|
}
|
|
}
|
|
else if (seenOptionalParameter && !parameter.initializer) {
|
|
return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] {
|
|
return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter));
|
|
}
|
|
|
|
function checkGrammarForUseStrictSimpleParameterList(node: FunctionLikeDeclaration): boolean {
|
|
if (languageVersion >= ScriptTarget.ES2016) {
|
|
const useStrictDirective = node.body && isBlock(node.body) && findUseStrictPrologue(node.body.statements);
|
|
if (useStrictDirective) {
|
|
const nonSimpleParameters = getNonSimpleParameters(node.parameters);
|
|
if (length(nonSimpleParameters)) {
|
|
forEach(nonSimpleParameters, parameter => {
|
|
addRelatedInfo(
|
|
error(parameter, Diagnostics.This_parameter_is_not_allowed_with_use_strict_directive),
|
|
createDiagnosticForNode(useStrictDirective, Diagnostics.use_strict_directive_used_here)
|
|
);
|
|
});
|
|
|
|
const diagnostics = nonSimpleParameters.map((parameter, index) => (
|
|
index === 0 ? createDiagnosticForNode(parameter, Diagnostics.Non_simple_parameter_declared_here) : createDiagnosticForNode(parameter, Diagnostics.and_here)
|
|
)) as [DiagnosticWithLocation, ...DiagnosticWithLocation[]];
|
|
addRelatedInfo(error(useStrictDirective, Diagnostics.use_strict_directive_cannot_be_used_with_non_simple_parameter_list), ...diagnostics);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean {
|
|
// Prevent cascading error by short-circuit
|
|
const file = getSourceFileOfNode(node);
|
|
return checkGrammarDecoratorsAndModifiers(node) || checkGrammarTypeParameterList(node.typeParameters, file) ||
|
|
checkGrammarParameterList(node.parameters) || checkGrammarArrowFunction(node, file) ||
|
|
(isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node));
|
|
}
|
|
|
|
function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean {
|
|
const file = getSourceFileOfNode(node);
|
|
return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(node.typeParameters, file);
|
|
}
|
|
|
|
function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean {
|
|
if (!isArrowFunction(node)) {
|
|
return false;
|
|
}
|
|
|
|
const { equalsGreaterThanToken } = node;
|
|
const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line;
|
|
const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line;
|
|
return startLine !== endLine && grammarErrorOnNode(equalsGreaterThanToken, Diagnostics.Line_terminator_not_permitted_before_arrow);
|
|
}
|
|
|
|
function checkGrammarIndexSignatureParameters(node: SignatureDeclaration): boolean {
|
|
const parameter = node.parameters[0];
|
|
if (node.parameters.length !== 1) {
|
|
if (parameter) {
|
|
return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_must_have_exactly_one_parameter);
|
|
}
|
|
else {
|
|
return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter);
|
|
}
|
|
}
|
|
if (parameter.dotDotDotToken) {
|
|
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter);
|
|
}
|
|
if (hasModifiers(parameter)) {
|
|
return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier);
|
|
}
|
|
if (parameter.questionToken) {
|
|
return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark);
|
|
}
|
|
if (parameter.initializer) {
|
|
return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer);
|
|
}
|
|
if (!parameter.type) {
|
|
return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation);
|
|
}
|
|
if (parameter.type.kind !== SyntaxKind.StringKeyword && parameter.type.kind !== SyntaxKind.NumberKeyword) {
|
|
const type = getTypeFromTypeNode(parameter.type);
|
|
|
|
if (type.flags & TypeFlags.String || type.flags & TypeFlags.Number) {
|
|
return grammarErrorOnNode(parameter.name,
|
|
Diagnostics.An_index_signature_parameter_type_cannot_be_a_type_alias_Consider_writing_0_Colon_1_Colon_2_instead,
|
|
getTextOfNode(parameter.name),
|
|
typeToString(type),
|
|
typeToString(node.type ? getTypeFromTypeNode(node.type) : anyType));
|
|
}
|
|
|
|
if (type.flags & TypeFlags.Union && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true)) {
|
|
return grammarErrorOnNode(parameter.name,
|
|
Diagnostics.An_index_signature_parameter_type_cannot_be_a_union_type_Consider_using_a_mapped_object_type_instead);
|
|
}
|
|
|
|
return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_either_string_or_number);
|
|
}
|
|
if (!node.type) {
|
|
return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarIndexSignature(node: SignatureDeclaration) {
|
|
// Prevent cascading error by short-circuit
|
|
return checkGrammarDecoratorsAndModifiers(node) || checkGrammarIndexSignatureParameters(node);
|
|
}
|
|
|
|
function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray<TypeNode> | undefined): boolean {
|
|
if (typeArguments && typeArguments.length === 0) {
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
const start = typeArguments.pos - "<".length;
|
|
const end = skipTrivia(sourceFile.text, typeArguments.end) + ">".length;
|
|
return grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.Type_argument_list_cannot_be_empty);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray<TypeNode> | undefined): boolean {
|
|
return checkGrammarForDisallowedTrailingComma(typeArguments) ||
|
|
checkGrammarForAtLeastOneTypeArgument(node, typeArguments);
|
|
}
|
|
|
|
function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean {
|
|
if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) {
|
|
return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarForOmittedArgument(args: NodeArray<Expression> | undefined): boolean {
|
|
if (args) {
|
|
for (const arg of args) {
|
|
if (arg.kind === SyntaxKind.OmittedExpression) {
|
|
return grammarErrorAtPos(arg, arg.pos, 0, Diagnostics.Argument_expression_expected);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarArguments(args: NodeArray<Expression> | undefined): boolean {
|
|
return checkGrammarForOmittedArgument(args);
|
|
}
|
|
|
|
function checkGrammarHeritageClause(node: HeritageClause): boolean {
|
|
const types = node.types;
|
|
if (checkGrammarForDisallowedTrailingComma(types)) {
|
|
return true;
|
|
}
|
|
if (types && types.length === 0) {
|
|
const listType = tokenToString(node.token);
|
|
return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType);
|
|
}
|
|
return some(types, checkGrammarExpressionWithTypeArguments);
|
|
}
|
|
|
|
function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments) {
|
|
return checkGrammarTypeArguments(node, node.typeArguments);
|
|
}
|
|
|
|
function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) {
|
|
let seenExtendsClause = false;
|
|
let seenImplementsClause = false;
|
|
|
|
if (!checkGrammarDecoratorsAndModifiers(node) && node.heritageClauses) {
|
|
for (const heritageClause of node.heritageClauses) {
|
|
if (heritageClause.token === SyntaxKind.ExtendsKeyword) {
|
|
if (seenExtendsClause) {
|
|
return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen);
|
|
}
|
|
|
|
if (seenImplementsClause) {
|
|
return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause);
|
|
}
|
|
|
|
if (heritageClause.types.length > 1) {
|
|
return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class);
|
|
}
|
|
|
|
seenExtendsClause = true;
|
|
}
|
|
else {
|
|
Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword);
|
|
if (seenImplementsClause) {
|
|
return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen);
|
|
}
|
|
|
|
seenImplementsClause = true;
|
|
}
|
|
|
|
// Grammar checking heritageClause inside class declaration
|
|
checkGrammarHeritageClause(heritageClause);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) {
|
|
let seenExtendsClause = false;
|
|
|
|
if (node.heritageClauses) {
|
|
for (const heritageClause of node.heritageClauses) {
|
|
if (heritageClause.token === SyntaxKind.ExtendsKeyword) {
|
|
if (seenExtendsClause) {
|
|
return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen);
|
|
}
|
|
|
|
seenExtendsClause = true;
|
|
}
|
|
else {
|
|
Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword);
|
|
return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause);
|
|
}
|
|
|
|
// Grammar checking heritageClause inside class declaration
|
|
checkGrammarHeritageClause(heritageClause);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarComputedPropertyName(node: Node): boolean {
|
|
// If node is not a computedPropertyName, just skip the grammar checking
|
|
if (node.kind !== SyntaxKind.ComputedPropertyName) {
|
|
return false;
|
|
}
|
|
|
|
const computedPropertyName = <ComputedPropertyName>node;
|
|
if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>computedPropertyName.expression).operatorToken.kind === SyntaxKind.CommaToken) {
|
|
return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarForGenerator(node: FunctionLikeDeclaration) {
|
|
if (node.asteriskToken) {
|
|
Debug.assert(
|
|
node.kind === SyntaxKind.FunctionDeclaration ||
|
|
node.kind === SyntaxKind.FunctionExpression ||
|
|
node.kind === SyntaxKind.MethodDeclaration);
|
|
if (node.flags & NodeFlags.Ambient) {
|
|
return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_not_allowed_in_an_ambient_context);
|
|
}
|
|
if (!node.body) {
|
|
return grammarErrorOnNode(node.asteriskToken, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean {
|
|
return !!questionToken && grammarErrorOnNode(questionToken, message);
|
|
}
|
|
|
|
function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean {
|
|
return !!exclamationToken && grammarErrorOnNode(exclamationToken, message);
|
|
}
|
|
|
|
function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) {
|
|
const seen = createUnderscoreEscapedMap<DeclarationMeaning>();
|
|
|
|
for (const prop of node.properties) {
|
|
if (prop.kind === SyntaxKind.SpreadAssignment) {
|
|
if (inDestructuring) {
|
|
// a rest property cannot be destructured any further
|
|
const expression = skipParentheses(prop.expression);
|
|
if (isArrayLiteralExpression(expression) || isObjectLiteralExpression(expression)) {
|
|
return grammarErrorOnNode(prop.expression, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
const name = prop.name;
|
|
if (name.kind === SyntaxKind.ComputedPropertyName) {
|
|
// If the name is not a ComputedPropertyName, the grammar checking will skip it
|
|
checkGrammarComputedPropertyName(name);
|
|
}
|
|
|
|
if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) {
|
|
// having objectAssignmentInitializer is only valid in ObjectAssignmentPattern
|
|
// outside of destructuring it is a syntax error
|
|
return grammarErrorOnNode(prop.equalsToken!, Diagnostics.can_only_be_used_in_an_object_literal_property_inside_a_destructuring_assignment);
|
|
}
|
|
|
|
if (name.kind === SyntaxKind.PrivateIdentifier) {
|
|
return grammarErrorOnNode(name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
|
|
}
|
|
|
|
// Modifiers are never allowed on properties except for 'async' on a method declaration
|
|
if (prop.modifiers) {
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
for (const mod of prop.modifiers!) { // TODO: GH#19955
|
|
if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) {
|
|
grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ECMA-262 11.1.5 Object Initializer
|
|
// If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true
|
|
// a.This production is contained in strict code and IsDataDescriptor(previous) is true and
|
|
// IsDataDescriptor(propId.descriptor) is true.
|
|
// b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true.
|
|
// c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true.
|
|
// d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true
|
|
// and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields
|
|
let currentKind: DeclarationMeaning;
|
|
switch (prop.kind) {
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context);
|
|
// falls through
|
|
case SyntaxKind.PropertyAssignment:
|
|
// Grammar checking for computedPropertyName and shorthandPropertyAssignment
|
|
checkGrammarForInvalidQuestionMark(prop.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional);
|
|
if (name.kind === SyntaxKind.NumericLiteral) {
|
|
checkGrammarNumericLiteral(name);
|
|
}
|
|
currentKind = DeclarationMeaning.PropertyAssignment;
|
|
break;
|
|
case SyntaxKind.MethodDeclaration:
|
|
currentKind = DeclarationMeaning.Method;
|
|
break;
|
|
case SyntaxKind.GetAccessor:
|
|
currentKind = DeclarationMeaning.GetAccessor;
|
|
break;
|
|
case SyntaxKind.SetAccessor:
|
|
currentKind = DeclarationMeaning.SetAccessor;
|
|
break;
|
|
default:
|
|
throw Debug.assertNever(prop, "Unexpected syntax kind:" + (<Node>prop).kind);
|
|
}
|
|
|
|
if (!inDestructuring) {
|
|
const effectiveName = getPropertyNameForPropertyNameNode(name);
|
|
if (effectiveName === undefined) {
|
|
continue;
|
|
}
|
|
|
|
const existingKind = seen.get(effectiveName);
|
|
if (!existingKind) {
|
|
seen.set(effectiveName, currentKind);
|
|
}
|
|
else {
|
|
if ((currentKind & DeclarationMeaning.PropertyAssignmentOrMethod) && (existingKind & DeclarationMeaning.PropertyAssignmentOrMethod)) {
|
|
grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name));
|
|
}
|
|
else if ((currentKind & DeclarationMeaning.GetOrSetAccessor) && (existingKind & DeclarationMeaning.GetOrSetAccessor)) {
|
|
if (existingKind !== DeclarationMeaning.GetOrSetAccessor && currentKind !== existingKind) {
|
|
seen.set(effectiveName, currentKind | existingKind);
|
|
}
|
|
else {
|
|
return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name);
|
|
}
|
|
}
|
|
else {
|
|
return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarJsxElement(node: JsxOpeningLikeElement) {
|
|
checkGrammarTypeArguments(node, node.typeArguments);
|
|
const seen = createUnderscoreEscapedMap<boolean>();
|
|
|
|
for (const attr of node.attributes.properties) {
|
|
if (attr.kind === SyntaxKind.JsxSpreadAttribute) {
|
|
continue;
|
|
}
|
|
|
|
const { name, initializer } = attr;
|
|
if (!seen.get(name.escapedText)) {
|
|
seen.set(name.escapedText, true);
|
|
}
|
|
else {
|
|
return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name);
|
|
}
|
|
|
|
if (initializer && initializer.kind === SyntaxKind.JsxExpression && !initializer.expression) {
|
|
return grammarErrorOnNode(initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarJsxExpression(node: JsxExpression) {
|
|
if (node.expression && isCommaSequence(node.expression)) {
|
|
return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array);
|
|
}
|
|
}
|
|
|
|
function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean {
|
|
if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) {
|
|
return true;
|
|
}
|
|
|
|
if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) {
|
|
if ((forInOrOfStatement.flags & NodeFlags.AwaitContext) === NodeFlags.None) {
|
|
// use of 'for-await-of' in non-async function
|
|
const sourceFile = getSourceFileOfNode(forInOrOfStatement);
|
|
if (!hasParseDiagnostics(sourceFile)) {
|
|
const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.A_for_await_of_statement_is_only_allowed_within_an_async_function_or_async_generator);
|
|
const func = getContainingFunction(forInOrOfStatement);
|
|
if (func && func.kind !== SyntaxKind.Constructor) {
|
|
Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function.");
|
|
const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async);
|
|
addRelatedInfo(diagnostic, relatedInfo);
|
|
}
|
|
diagnostics.add(diagnostic);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
|
const variableList = <VariableDeclarationList>forInOrOfStatement.initializer;
|
|
if (!checkGrammarVariableDeclarationList(variableList)) {
|
|
const declarations = variableList.declarations;
|
|
|
|
// declarations.length can be zero if there is an error in variable declaration in for-of or for-in
|
|
// See http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements for details
|
|
// For example:
|
|
// var let = 10;
|
|
// for (let of [1,2,3]) {} // this is invalid ES6 syntax
|
|
// for (let in [1,2,3]) {} // this is invalid ES6 syntax
|
|
// We will then want to skip on grammar checking on variableList declaration
|
|
if (!declarations.length) {
|
|
return false;
|
|
}
|
|
|
|
if (declarations.length > 1) {
|
|
const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement
|
|
? Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement
|
|
: Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement;
|
|
return grammarErrorOnFirstToken(variableList.declarations[1], diagnostic);
|
|
}
|
|
const firstDeclaration = declarations[0];
|
|
|
|
if (firstDeclaration.initializer) {
|
|
const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement
|
|
? Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer
|
|
: Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer;
|
|
return grammarErrorOnNode(firstDeclaration.name, diagnostic);
|
|
}
|
|
if (firstDeclaration.type) {
|
|
const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement
|
|
? Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation
|
|
: Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation;
|
|
return grammarErrorOnNode(firstDeclaration, diagnostic);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarAccessor(accessor: AccessorDeclaration): boolean {
|
|
if (!(accessor.flags & NodeFlags.Ambient)) {
|
|
if (languageVersion < ScriptTarget.ES5) {
|
|
return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher);
|
|
}
|
|
if (accessor.body === undefined && !hasModifier(accessor, ModifierFlags.Abstract)) {
|
|
return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{");
|
|
}
|
|
}
|
|
if (accessor.body && hasModifier(accessor, ModifierFlags.Abstract)) {
|
|
return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation);
|
|
}
|
|
if (accessor.typeParameters) {
|
|
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters);
|
|
}
|
|
if (!doesAccessorHaveCorrectParameterCount(accessor)) {
|
|
return grammarErrorOnNode(accessor.name,
|
|
accessor.kind === SyntaxKind.GetAccessor ?
|
|
Diagnostics.A_get_accessor_cannot_have_parameters :
|
|
Diagnostics.A_set_accessor_must_have_exactly_one_parameter);
|
|
}
|
|
if (accessor.kind === SyntaxKind.SetAccessor) {
|
|
if (accessor.type) {
|
|
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation);
|
|
}
|
|
const parameter = Debug.checkDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion.");
|
|
if (parameter.dotDotDotToken) {
|
|
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter);
|
|
}
|
|
if (parameter.questionToken) {
|
|
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter);
|
|
}
|
|
if (parameter.initializer) {
|
|
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Does the accessor have the right number of parameters?
|
|
* A get accessor has no parameters or a single `this` parameter.
|
|
* A set accessor has one parameter or a `this` parameter and one more parameter.
|
|
*/
|
|
function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) {
|
|
return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1);
|
|
}
|
|
|
|
function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined {
|
|
if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) {
|
|
return getThisParameter(accessor);
|
|
}
|
|
}
|
|
|
|
function checkGrammarTypeOperatorNode(node: TypeOperatorNode) {
|
|
if (node.operator === SyntaxKind.UniqueKeyword) {
|
|
if (node.type.kind !== SyntaxKind.SymbolKeyword) {
|
|
return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword));
|
|
}
|
|
|
|
const parent = walkUpParenthesizedTypes(node.parent);
|
|
switch (parent.kind) {
|
|
case SyntaxKind.VariableDeclaration:
|
|
const decl = parent as VariableDeclaration;
|
|
if (decl.name.kind !== SyntaxKind.Identifier) {
|
|
return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name);
|
|
}
|
|
if (!isVariableDeclarationInVariableStatement(decl)) {
|
|
return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement);
|
|
}
|
|
if (!(decl.parent.flags & NodeFlags.Const)) {
|
|
return grammarErrorOnNode((<VariableDeclaration>parent).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const);
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
|
if (!hasModifier(parent, ModifierFlags.Static) ||
|
|
!hasModifier(parent, ModifierFlags.Readonly)) {
|
|
return grammarErrorOnNode((<PropertyDeclaration>parent).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly);
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.PropertySignature:
|
|
if (!hasModifier(parent, ModifierFlags.Readonly)) {
|
|
return grammarErrorOnNode((<PropertySignature>parent).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here);
|
|
}
|
|
}
|
|
else if (node.operator === SyntaxKind.ReadonlyKeyword) {
|
|
if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) {
|
|
return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_tuple_literal_types, tokenToString(SyntaxKind.SymbolKeyword));
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) {
|
|
if (isNonBindableDynamicName(node)) {
|
|
return grammarErrorOnNode(node, message);
|
|
}
|
|
}
|
|
|
|
function checkGrammarMethod(node: MethodDeclaration | MethodSignature) {
|
|
if (checkGrammarFunctionLikeDeclaration(node)) {
|
|
return true;
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.MethodDeclaration) {
|
|
if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
// We only disallow modifier on a method declaration if it is a property of object-literal-expression
|
|
if (node.modifiers && !(node.modifiers.length === 1 && first(node.modifiers).kind === SyntaxKind.AsyncKeyword)) {
|
|
return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here);
|
|
}
|
|
else if (checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) {
|
|
return true;
|
|
}
|
|
else if (checkGrammarForInvalidExclamationToken(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context)) {
|
|
return true;
|
|
}
|
|
else if (node.body === undefined) {
|
|
return grammarErrorAtPos(node, node.end - 1, ";".length, Diagnostics._0_expected, "{");
|
|
}
|
|
}
|
|
if (checkGrammarForGenerator(node)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (isClassLike(node.parent)) {
|
|
// Technically, computed properties in ambient contexts is disallowed
|
|
// for property declarations and accessors too, not just methods.
|
|
// However, property declarations disallow computed names in general,
|
|
// and accessors are not allowed in ambient contexts in general,
|
|
// so this error only really matters for methods.
|
|
if (node.flags & NodeFlags.Ambient) {
|
|
return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_ambient_context_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type);
|
|
}
|
|
else if (node.kind === SyntaxKind.MethodDeclaration && !node.body) {
|
|
return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_method_overload_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type);
|
|
}
|
|
}
|
|
else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) {
|
|
return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type);
|
|
}
|
|
else if (node.parent.kind === SyntaxKind.TypeLiteral) {
|
|
return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type);
|
|
}
|
|
}
|
|
|
|
function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean {
|
|
let current: Node = node;
|
|
while (current) {
|
|
if (isFunctionLike(current)) {
|
|
return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary);
|
|
}
|
|
|
|
switch (current.kind) {
|
|
case SyntaxKind.LabeledStatement:
|
|
if (node.label && (<LabeledStatement>current).label.escapedText === node.label.escapedText) {
|
|
// found matching label - verify that label usage is correct
|
|
// continue can only target labels that are on iteration statements
|
|
const isMisplacedContinueLabel = node.kind === SyntaxKind.ContinueStatement
|
|
&& !isIterationStatement((<LabeledStatement>current).statement, /*lookInLabeledStatement*/ true);
|
|
|
|
if (isMisplacedContinueLabel) {
|
|
return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
case SyntaxKind.SwitchStatement:
|
|
if (node.kind === SyntaxKind.BreakStatement && !node.label) {
|
|
// unlabeled break within switch statement - ok
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
if (isIterationStatement(current, /*lookInLabeledStatement*/ false) && !node.label) {
|
|
// unlabeled break or continue within iteration statement - ok
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
current = current.parent;
|
|
}
|
|
|
|
if (node.label) {
|
|
const message = node.kind === SyntaxKind.BreakStatement
|
|
? Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement
|
|
: Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement;
|
|
|
|
return grammarErrorOnNode(node, message);
|
|
}
|
|
else {
|
|
const message = node.kind === SyntaxKind.BreakStatement
|
|
? Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement
|
|
: Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement;
|
|
return grammarErrorOnNode(node, message);
|
|
}
|
|
}
|
|
|
|
function checkGrammarBindingElement(node: BindingElement) {
|
|
if (node.dotDotDotToken) {
|
|
const elements = node.parent.elements;
|
|
if (node !== last(elements)) {
|
|
return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
|
|
}
|
|
checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
|
|
|
|
if (node.propertyName) {
|
|
return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name);
|
|
}
|
|
|
|
if (node.initializer) {
|
|
// Error on equals token which immediately precedes the initializer
|
|
return grammarErrorAtPos(node, node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isStringOrNumberLiteralExpression(expr: Expression) {
|
|
return isStringOrNumericLiteralLike(expr) ||
|
|
expr.kind === SyntaxKind.PrefixUnaryExpression && (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
|
|
(<PrefixUnaryExpression>expr).operand.kind === SyntaxKind.NumericLiteral;
|
|
}
|
|
|
|
function isBigIntLiteralExpression(expr: Expression) {
|
|
return expr.kind === SyntaxKind.BigIntLiteral ||
|
|
expr.kind === SyntaxKind.PrefixUnaryExpression && (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
|
|
(<PrefixUnaryExpression>expr).operand.kind === SyntaxKind.BigIntLiteral;
|
|
}
|
|
|
|
function isSimpleLiteralEnumReference(expr: Expression) {
|
|
if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) &&
|
|
isEntityNameExpression(expr.expression)) {
|
|
return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral);
|
|
}
|
|
}
|
|
|
|
function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) {
|
|
const {initializer} = node;
|
|
if (initializer) {
|
|
const isInvalidInitializer = !(
|
|
isStringOrNumberLiteralExpression(initializer) ||
|
|
isSimpleLiteralEnumReference(initializer) ||
|
|
initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword ||
|
|
isBigIntLiteralExpression(initializer)
|
|
);
|
|
const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node);
|
|
if (isConstOrReadonly && !node.type) {
|
|
if (isInvalidInitializer) {
|
|
return grammarErrorOnNode(initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference);
|
|
}
|
|
}
|
|
else {
|
|
return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts);
|
|
}
|
|
if (!isConstOrReadonly || isInvalidInitializer) {
|
|
return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarVariableDeclaration(node: VariableDeclaration) {
|
|
if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) {
|
|
if (node.flags & NodeFlags.Ambient) {
|
|
checkAmbientInitializer(node);
|
|
}
|
|
else if (!node.initializer) {
|
|
if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) {
|
|
return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer);
|
|
}
|
|
if (isVarConst(node)) {
|
|
return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) {
|
|
return grammarErrorOnNode(node.exclamationToken, Diagnostics.Definite_assignment_assertions_can_only_be_used_along_with_a_type_annotation);
|
|
}
|
|
|
|
const moduleKind = getEmitModuleKind(compilerOptions);
|
|
|
|
if (moduleKind < ModuleKind.ES2015 && moduleKind !== ModuleKind.System && !compilerOptions.noEmit &&
|
|
!(node.parent.parent.flags & NodeFlags.Ambient) && hasModifier(node.parent.parent, ModifierFlags.Export)) {
|
|
checkESModuleMarker(node.name);
|
|
}
|
|
|
|
const checkLetConstNames = (isLet(node) || isVarConst(node));
|
|
|
|
// 1. LexicalDeclaration : LetOrConst BindingList ;
|
|
// It is a Syntax Error if the BoundNames of BindingList contains "let".
|
|
// 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding
|
|
// It is a Syntax Error if the BoundNames of ForDeclaration contains "let".
|
|
|
|
// It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code
|
|
// and its Identifier is eval or arguments
|
|
return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name);
|
|
}
|
|
|
|
function checkESModuleMarker(name: Identifier | BindingPattern): boolean {
|
|
if (name.kind === SyntaxKind.Identifier) {
|
|
if (idText(name) === "__esModule") {
|
|
return grammarErrorOnNode(name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules);
|
|
}
|
|
}
|
|
else {
|
|
const elements = name.elements;
|
|
for (const element of elements) {
|
|
if (!isOmittedExpression(element)) {
|
|
return checkESModuleMarker(element.name);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean {
|
|
if (name.kind === SyntaxKind.Identifier) {
|
|
if (name.originalKeywordKind === SyntaxKind.LetKeyword) {
|
|
return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations);
|
|
}
|
|
}
|
|
else {
|
|
const elements = name.elements;
|
|
for (const element of elements) {
|
|
if (!isOmittedExpression(element)) {
|
|
checkGrammarNameInLetOrConstDeclarations(element.name);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean {
|
|
const declarations = declarationList.declarations;
|
|
if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) {
|
|
return true;
|
|
}
|
|
|
|
if (!declarationList.declarations.length) {
|
|
return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function allowLetAndConstDeclarations(parent: Node): boolean {
|
|
switch (parent.kind) {
|
|
case SyntaxKind.IfStatement:
|
|
case SyntaxKind.DoStatement:
|
|
case SyntaxKind.WhileStatement:
|
|
case SyntaxKind.WithStatement:
|
|
case SyntaxKind.ForStatement:
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
return false;
|
|
case SyntaxKind.LabeledStatement:
|
|
return allowLetAndConstDeclarations(parent.parent);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function checkGrammarForDisallowedLetOrConstStatement(node: VariableStatement) {
|
|
if (!allowLetAndConstDeclarations(node.parent)) {
|
|
if (isLet(node.declarationList)) {
|
|
return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block);
|
|
}
|
|
else if (isVarConst(node.declarationList)) {
|
|
return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGrammarMetaProperty(node: MetaProperty) {
|
|
const escapedText = node.name.escapedText;
|
|
switch (node.keywordToken) {
|
|
case SyntaxKind.NewKeyword:
|
|
if (escapedText !== "target") {
|
|
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target");
|
|
}
|
|
break;
|
|
case SyntaxKind.ImportKeyword:
|
|
if (escapedText !== "meta") {
|
|
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function hasParseDiagnostics(sourceFile: SourceFile): boolean {
|
|
return sourceFile.parseDiagnostics.length > 0;
|
|
}
|
|
|
|
function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
if (!hasParseDiagnostics(sourceFile)) {
|
|
const span = getSpanOfTokenAtPosition(sourceFile, node.pos);
|
|
diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
|
|
const sourceFile = getSourceFileOfNode(nodeForSourceFile);
|
|
if (!hasParseDiagnostics(sourceFile)) {
|
|
diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
if (!hasParseDiagnostics(sourceFile)) {
|
|
diagnostics.add(createDiagnosticForNode(node, message, arg0, arg1, arg2));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) {
|
|
const jsdocTypeParameters = isInJSFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined;
|
|
const range = node.typeParameters || jsdocTypeParameters && firstOrUndefined(jsdocTypeParameters);
|
|
if (range) {
|
|
const pos = range.pos === range.end ? range.pos : skipTrivia(getSourceFileOfNode(node).text, range.pos);
|
|
return grammarErrorAtPos(node, pos, range.end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration);
|
|
}
|
|
}
|
|
|
|
function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) {
|
|
const type = getEffectiveReturnTypeNode(node);
|
|
if (type) {
|
|
return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration);
|
|
}
|
|
}
|
|
|
|
function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) {
|
|
if (isClassLike(node.parent)) {
|
|
if (isStringLiteral(node.name) && node.name.text === "constructor") {
|
|
return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor);
|
|
}
|
|
if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) {
|
|
return true;
|
|
}
|
|
if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) {
|
|
return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher);
|
|
}
|
|
}
|
|
else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) {
|
|
if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) {
|
|
return true;
|
|
}
|
|
if (node.initializer) {
|
|
return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer);
|
|
}
|
|
}
|
|
else if (node.parent.kind === SyntaxKind.TypeLiteral) {
|
|
if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) {
|
|
return true;
|
|
}
|
|
if (node.initializer) {
|
|
return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer);
|
|
}
|
|
}
|
|
|
|
if (node.flags & NodeFlags.Ambient) {
|
|
checkAmbientInitializer(node);
|
|
}
|
|
|
|
if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer ||
|
|
node.flags & NodeFlags.Ambient || hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract))) {
|
|
return grammarErrorOnNode(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context);
|
|
}
|
|
}
|
|
|
|
function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean {
|
|
// A declare modifier is required for any top level .d.ts declaration except export=, export default, export as namespace
|
|
// interfaces and imports categories:
|
|
//
|
|
// DeclarationElement:
|
|
// ExportAssignment
|
|
// export_opt InterfaceDeclaration
|
|
// export_opt TypeAliasDeclaration
|
|
// export_opt ImportDeclaration
|
|
// export_opt ExternalImportDeclaration
|
|
// export_opt AmbientDeclaration
|
|
//
|
|
// TODO: The spec needs to be amended to reflect this grammar.
|
|
if (node.kind === SyntaxKind.InterfaceDeclaration ||
|
|
node.kind === SyntaxKind.TypeAliasDeclaration ||
|
|
node.kind === SyntaxKind.ImportDeclaration ||
|
|
node.kind === SyntaxKind.ImportEqualsDeclaration ||
|
|
node.kind === SyntaxKind.ExportDeclaration ||
|
|
node.kind === SyntaxKind.ExportAssignment ||
|
|
node.kind === SyntaxKind.NamespaceExportDeclaration ||
|
|
hasModifier(node, ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) {
|
|
return false;
|
|
}
|
|
|
|
return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier);
|
|
}
|
|
|
|
function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean {
|
|
for (const decl of file.statements) {
|
|
if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) {
|
|
if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarSourceFile(node: SourceFile): boolean {
|
|
return !!(node.flags & NodeFlags.Ambient) && checkGrammarTopLevelElementsForRequiredDeclareModifier(node);
|
|
}
|
|
|
|
function checkGrammarStatementInAmbientContext(node: Node): boolean {
|
|
if (node.flags & NodeFlags.Ambient) {
|
|
// Find containing block which is either Block, ModuleBlock, SourceFile
|
|
const links = getNodeLinks(node);
|
|
if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) {
|
|
return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts);
|
|
}
|
|
|
|
// We are either parented by another statement, or some sort of block.
|
|
// If we're in a block, we only want to really report an error once
|
|
// to prevent noisiness. So use a bit on the block to indicate if
|
|
// this has already been reported, and don't report if it has.
|
|
//
|
|
if (node.parent.kind === SyntaxKind.Block || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) {
|
|
const links = getNodeLinks(node.parent);
|
|
// Check if the containing block ever report this error
|
|
if (!links.hasReportedStatementInAmbientContext) {
|
|
return links.hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.Statements_are_not_allowed_in_ambient_contexts);
|
|
}
|
|
}
|
|
else {
|
|
// We must be parented by a statement. If so, there's no need
|
|
// to report the error as our parent will have already done it.
|
|
// Debug.assert(isStatement(node.parent));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarNumericLiteral(node: NumericLiteral): boolean {
|
|
// Grammar checking
|
|
if (node.numericLiteralFlags & TokenFlags.Octal) {
|
|
let diagnosticMessage: DiagnosticMessage | undefined;
|
|
if (languageVersion >= ScriptTarget.ES5) {
|
|
diagnosticMessage = Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher_Use_the_syntax_0;
|
|
}
|
|
else if (isChildOfNodeWithKind(node, SyntaxKind.LiteralType)) {
|
|
diagnosticMessage = Diagnostics.Octal_literal_types_must_use_ES2015_syntax_Use_the_syntax_0;
|
|
}
|
|
else if (isChildOfNodeWithKind(node, SyntaxKind.EnumMember)) {
|
|
diagnosticMessage = Diagnostics.Octal_literals_are_not_allowed_in_enums_members_initializer_Use_the_syntax_0;
|
|
}
|
|
if (diagnosticMessage) {
|
|
const withMinus = isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.MinusToken;
|
|
const literal = (withMinus ? "-" : "") + "0o" + node.text;
|
|
return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal);
|
|
}
|
|
}
|
|
|
|
// Realism (size) checking
|
|
checkNumericLiteralValueSize(node);
|
|
|
|
return false;
|
|
}
|
|
|
|
function checkNumericLiteralValueSize(node: NumericLiteral) {
|
|
// Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint
|
|
// Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1
|
|
// Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway
|
|
if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) {
|
|
return;
|
|
}
|
|
|
|
// We can't rely on the runtime to accurately store and compare extremely large numeric values
|
|
// Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298
|
|
// Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER,
|
|
// it's likely addition operations on it will fail too
|
|
const apparentValue = +getTextOfNode(node);
|
|
if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) {
|
|
return;
|
|
}
|
|
|
|
addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers));
|
|
}
|
|
|
|
function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean {
|
|
const literalType = isLiteralTypeNode(node.parent) ||
|
|
isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent);
|
|
if (!literalType) {
|
|
if (languageVersion < ScriptTarget.ES2020) {
|
|
if (grammarErrorOnNode(node, Diagnostics.BigInt_literals_are_not_available_when_targeting_lower_than_ES2020)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
|
|
const sourceFile = getSourceFileOfNode(node);
|
|
if (!hasParseDiagnostics(sourceFile)) {
|
|
const span = getSpanOfTokenAtPosition(sourceFile, node.pos);
|
|
diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, arg0, arg1, arg2));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getAmbientModules(): Symbol[] {
|
|
if (!ambientModulesCache) {
|
|
ambientModulesCache = [];
|
|
globals.forEach((global, sym) => {
|
|
// No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module.
|
|
if (ambientModuleSymbolRegex.test(sym as string)) {
|
|
ambientModulesCache!.push(global);
|
|
}
|
|
});
|
|
}
|
|
return ambientModulesCache;
|
|
}
|
|
|
|
function checkGrammarImportClause(node: ImportClause): boolean {
|
|
if (node.isTypeOnly && node.name && node.namedBindings) {
|
|
return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkGrammarImportCallExpression(node: ImportCall): boolean {
|
|
if (moduleKind === ModuleKind.ES2015) {
|
|
return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_esnext_commonjs_amd_system_or_umd);
|
|
}
|
|
|
|
if (node.typeArguments) {
|
|
return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_have_type_arguments);
|
|
}
|
|
|
|
const nodeArguments = node.arguments;
|
|
if (nodeArguments.length !== 1) {
|
|
return grammarErrorOnNode(node, Diagnostics.Dynamic_import_must_have_one_specifier_as_an_argument);
|
|
}
|
|
checkGrammarForDisallowedTrailingComma(nodeArguments);
|
|
// see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import.
|
|
// parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import.
|
|
if (isSpreadElement(nodeArguments[0])) {
|
|
return grammarErrorOnNode(nodeArguments[0], Diagnostics.Specifier_of_dynamic_import_cannot_be_spread_element);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) {
|
|
const sourceObjectFlags = getObjectFlags(source);
|
|
if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) {
|
|
return find(unionTarget.types, target => {
|
|
if (target.flags & TypeFlags.Object) {
|
|
const overlapObjFlags = sourceObjectFlags & getObjectFlags(target);
|
|
if (overlapObjFlags & ObjectFlags.Reference) {
|
|
return (source as TypeReference).target === (target as TypeReference).target;
|
|
}
|
|
if (overlapObjFlags & ObjectFlags.Anonymous) {
|
|
return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) {
|
|
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && forEachType(unionTarget, isArrayLikeType)) {
|
|
return find(unionTarget.types, t => !isArrayLikeType(t));
|
|
}
|
|
}
|
|
|
|
function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) {
|
|
let signatureKind = SignatureKind.Call;
|
|
const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 ||
|
|
(signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0);
|
|
if (hasSignatures) {
|
|
return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0);
|
|
}
|
|
}
|
|
|
|
function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) {
|
|
let bestMatch: Type | undefined;
|
|
let matchingCount = 0;
|
|
for (const target of unionTarget.types) {
|
|
const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]);
|
|
if (overlap.flags & TypeFlags.Index) {
|
|
// perfect overlap of keys
|
|
bestMatch = target;
|
|
matchingCount = Infinity;
|
|
}
|
|
else if (overlap.flags & TypeFlags.Union) {
|
|
// We only want to account for literal types otherwise.
|
|
// If we have a union of index types, it seems likely that we
|
|
// needed to elaborate between two generic mapped types anyway.
|
|
const len = length(filter((overlap as UnionType).types, isUnitType));
|
|
if (len >= matchingCount) {
|
|
bestMatch = target;
|
|
matchingCount = len;
|
|
}
|
|
}
|
|
else if (isUnitType(overlap) && 1 >= matchingCount) {
|
|
bestMatch = target;
|
|
matchingCount = 1;
|
|
}
|
|
}
|
|
return bestMatch;
|
|
}
|
|
|
|
function filterPrimitivesIfContainsNonPrimitive(type: UnionType) {
|
|
if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) {
|
|
const result = filterType(type, t => !(t.flags & TypeFlags.Primitive));
|
|
if (!(result.flags & TypeFlags.Never)) {
|
|
return result;
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
// Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly
|
|
function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary) {
|
|
if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) {
|
|
const sourceProperties = getPropertiesOfType(source);
|
|
if (sourceProperties) {
|
|
const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target);
|
|
if (sourcePropertiesFiltered) {
|
|
return discriminateTypeByDiscriminableItems(<UnionType>target, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo);
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function isNotAccessor(declaration: Declaration): boolean {
|
|
// Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks
|
|
return !isAccessor(declaration);
|
|
}
|
|
|
|
function isNotOverload(declaration: Declaration): boolean {
|
|
return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) ||
|
|
!!(declaration as FunctionDeclaration).body;
|
|
}
|
|
|
|
/** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */
|
|
function isDeclarationNameOrImportPropertyName(name: Node): boolean {
|
|
switch (name.parent.kind) {
|
|
case SyntaxKind.ImportSpecifier:
|
|
case SyntaxKind.ExportSpecifier:
|
|
return isIdentifier(name);
|
|
default:
|
|
return isDeclarationName(name);
|
|
}
|
|
}
|
|
|
|
function isSomeImportDeclaration(decl: Node): boolean {
|
|
switch (decl.kind) {
|
|
case SyntaxKind.ImportClause: // For default import
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
case SyntaxKind.NamespaceImport:
|
|
case SyntaxKind.ImportSpecifier: // For rename import `x as y`
|
|
return true;
|
|
case SyntaxKind.Identifier:
|
|
// For regular import, `decl` is an Identifier under the ImportSpecifier.
|
|
return decl.parent.kind === SyntaxKind.ImportSpecifier;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
namespace JsxNames {
|
|
export const JSX = "JSX" as __String;
|
|
export const IntrinsicElements = "IntrinsicElements" as __String;
|
|
export const ElementClass = "ElementClass" as __String;
|
|
export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support
|
|
export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String;
|
|
export const Element = "Element" as __String;
|
|
export const IntrinsicAttributes = "IntrinsicAttributes" as __String;
|
|
export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String;
|
|
export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String;
|
|
}
|
|
|
|
function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) {
|
|
switch (typeKind) {
|
|
case IterationTypeKind.Yield: return "yieldType";
|
|
case IterationTypeKind.Return: return "returnType";
|
|
case IterationTypeKind.Next: return "nextType";
|
|
}
|
|
}
|
|
|
|
export function signatureHasRestParameter(s: Signature) {
|
|
return !!(s.flags & SignatureFlags.HasRestParameter);
|
|
}
|
|
|
|
export function signatureHasLiteralTypes(s: Signature) {
|
|
return !!(s.flags & SignatureFlags.HasLiteralTypes);
|
|
}
|
|
|
|
}
|