Skip to content

Commit

Permalink
Merge branch 'master' into noConstraint-is-unknown
Browse files Browse the repository at this point in the history
  • Loading branch information
weswigham committed Mar 28, 2019
2 parents a560fd6 + bb5eb02 commit 5224a6a
Show file tree
Hide file tree
Showing 23 changed files with 1,457 additions and 103 deletions.
98 changes: 52 additions & 46 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,10 @@ namespace ts {
return findAncestor(node.parent, isFunctionLike);
}

export function getContainingFunctionDeclaration(node: Node): FunctionLikeDeclaration | undefined {
return findAncestor(node.parent, isFunctionLikeDeclaration);
}

export function getContainingClass(node: Node): ClassLikeDeclaration | undefined {
return findAncestor(node.parent, isClassLike);
}
Expand Down
120 changes: 81 additions & 39 deletions src/services/refactors/convertParamsToDestructuredObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ namespace ts.refactor.convertParamsToDestructuredObject {
function groupReferences(referenceEntries: ReadonlyArray<FindAllReferences.Entry>): GroupedReferences {
const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] };
const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true };
const functionSymbols = map(functionNames, checker.getSymbolAtLocation);
const classSymbols = map(classNames, checker.getSymbolAtLocation);
const functionSymbols = map(functionNames, getSymbolTargetAtLocation);
const classSymbols = map(classNames, getSymbolTargetAtLocation);
const isConstructor = isConstructorDeclaration(functionDeclaration);

for (const entry of referenceEntries) {
Expand All @@ -111,7 +111,11 @@ namespace ts.refactor.convertParamsToDestructuredObject {
So we need to add a special case for this because when calling a constructor of a class through one of its subclasses,
the symbols are going to be different.
*/
if (contains(functionSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer) || isNewExpressionTarget(entry.node)) {
if (contains(functionSymbols, getSymbolTargetAtLocation(entry.node)) || isNewExpressionTarget(entry.node)) {
const importOrExportReference = entryToImportOrExport(entry);
if (importOrExportReference) {
continue;
}
const decl = entryToDeclaration(entry);
if (decl) {
groupedReferences.declarations.push(decl);
Expand All @@ -125,7 +129,12 @@ namespace ts.refactor.convertParamsToDestructuredObject {
}
}
// if the refactored function is a constructor, we must also check if the references to its class are valid
if (isConstructor && contains(classSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) {
if (isConstructor && contains(classSymbols, getSymbolTargetAtLocation(entry.node))) {
const importOrExportReference = entryToImportOrExport(entry);
if (importOrExportReference) {
continue;
}

const decl = entryToDeclaration(entry);
if (decl) {
groupedReferences.declarations.push(decl);
Expand Down Expand Up @@ -153,10 +162,27 @@ namespace ts.refactor.convertParamsToDestructuredObject {

return groupedReferences;
}

function getSymbolTargetAtLocation(node: Node) {
const symbol = checker.getSymbolAtLocation(node);
return symbol && getSymbolTarget(symbol, checker);
}
}

function symbolComparer(a: Symbol, b: Symbol): boolean {
return getSymbolTarget(a) === getSymbolTarget(b);
function entryToImportOrExport(entry: FindAllReferences.NodeEntry): Node | undefined {
const node = entry.node;

if (isImportSpecifier(node.parent)
|| isImportClause(node.parent)
|| isImportEqualsDeclaration(node.parent)
|| isNamespaceImport(node.parent)) {
return node;
}

if (isExportSpecifier(node.parent) || isExportAssignment(node.parent)) {
return node;
}
return undefined;
}

function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined {
Expand All @@ -171,37 +197,31 @@ namespace ts.refactor.convertParamsToDestructuredObject {
const functionReference = entry.node;
const parent = functionReference.parent;
switch (parent.kind) {
// Function call (foo(...) or super(...))
// foo(...) or super(...) or new Foo(...)
case SyntaxKind.CallExpression:
const callExpression = tryCast(parent, isCallExpression);
if (callExpression && callExpression.expression === functionReference) {
return callExpression;
}
break;
// Constructor call (new Foo(...))
case SyntaxKind.NewExpression:
const newExpression = tryCast(parent, isNewExpression);
if (newExpression && newExpression.expression === functionReference) {
return newExpression;
const callOrNewExpression = tryCast(parent, isCallOrNewExpression);
if (callOrNewExpression && callOrNewExpression.expression === functionReference) {
return callOrNewExpression;
}
break;
// Method call (x.foo(...))
// x.foo(...)
case SyntaxKind.PropertyAccessExpression:
const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression);
if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) {
const callExpression = tryCast(propertyAccessExpression.parent, isCallExpression);
if (callExpression && callExpression.expression === propertyAccessExpression) {
return callExpression;
const callOrNewExpression = tryCast(propertyAccessExpression.parent, isCallOrNewExpression);
if (callOrNewExpression && callOrNewExpression.expression === propertyAccessExpression) {
return callOrNewExpression;
}
}
break;
// Method call (x["foo"](...))
// x["foo"](...)
case SyntaxKind.ElementAccessExpression:
const elementAccessExpression = tryCast(parent, isElementAccessExpression);
if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) {
const callExpression = tryCast(elementAccessExpression.parent, isCallExpression);
if (callExpression && callExpression.expression === elementAccessExpression) {
return callExpression;
const callOrNewExpression = tryCast(elementAccessExpression.parent, isCallOrNewExpression);
if (callOrNewExpression && callOrNewExpression.expression === elementAccessExpression) {
return callOrNewExpression;
}
}
break;
Expand Down Expand Up @@ -244,7 +264,7 @@ namespace ts.refactor.convertParamsToDestructuredObject {

function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined {
const node = getTouchingToken(file, startPosition);
const functionDeclaration = getContainingFunction(node);
const functionDeclaration = getContainingFunctionDeclaration(node);

// don't offer refactor on top-level JSDoc
if (isTopLevelJSDoc(node)) return undefined;
Expand All @@ -267,25 +287,21 @@ namespace ts.refactor.convertParamsToDestructuredObject {
}

function isValidFunctionDeclaration(
functionDeclaration: SignatureDeclaration,
functionDeclaration: FunctionLikeDeclaration,
checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration {
if (!isValidParameterNodeArray(functionDeclaration.parameters, checker)) return false;
switch (functionDeclaration.kind) {
case SyntaxKind.FunctionDeclaration:
return hasNameOrDefault(functionDeclaration) && isSingleImplementation(functionDeclaration, checker);
case SyntaxKind.MethodDeclaration:
return !!functionDeclaration.name
&& !!functionDeclaration.body
&& !checker.isImplementationOfOverload(functionDeclaration);
return isSingleImplementation(functionDeclaration, checker);
case SyntaxKind.Constructor:
if (isClassDeclaration(functionDeclaration.parent)) {
return !!functionDeclaration.body
&& !!functionDeclaration.parent.name
&& !checker.isImplementationOfOverload(functionDeclaration);
return hasNameOrDefault(functionDeclaration.parent) && isSingleImplementation(functionDeclaration, checker);
}
else {
return isValidVariableDeclaration(functionDeclaration.parent.parent)
&& !!functionDeclaration.body
&& !checker.isImplementationOfOverload(functionDeclaration);
&& isSingleImplementation(functionDeclaration, checker);
}
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
Expand All @@ -294,6 +310,18 @@ namespace ts.refactor.convertParamsToDestructuredObject {
return false;
}

function isSingleImplementation(functionDeclaration: FunctionLikeDeclaration, checker: TypeChecker): boolean {
return !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration);
}

function hasNameOrDefault(functionOrClassDeclaration: FunctionDeclaration | ClassDeclaration): boolean {
if (!functionOrClassDeclaration.name) {
const defaultKeyword = findModifier(functionOrClassDeclaration, SyntaxKind.DefaultKeyword);
return !!defaultKeyword;
}
return true;
}

function isValidParameterNodeArray(
parameters: NodeArray<ParameterDeclaration>,
checker: TypeChecker): parameters is ValidParameterNodeArray {
Expand Down Expand Up @@ -488,11 +516,17 @@ namespace ts.refactor.convertParamsToDestructuredObject {
return getTextOfIdentifierOrLiteral(paramDeclaration.name);
}

function getClassNames(constructorDeclaration: ValidConstructor): Identifier[] {
function getClassNames(constructorDeclaration: ValidConstructor): (Identifier | Modifier)[] {
switch (constructorDeclaration.parent.kind) {
case SyntaxKind.ClassDeclaration:
const classDeclaration = constructorDeclaration.parent;
return [classDeclaration.name];
if (classDeclaration.name) return [classDeclaration.name];
// If the class declaration doesn't have a name, it should have a default modifier.
// We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault`
const defaultModifier = Debug.assertDefined(
findModifier(classDeclaration, SyntaxKind.DefaultKeyword),
"Nameless class declaration should be a default export");
return [defaultModifier];
case SyntaxKind.ClassExpression:
const classExpression = constructorDeclaration.parent;
const variableDeclaration = constructorDeclaration.parent.parent;
Expand All @@ -505,10 +539,19 @@ namespace ts.refactor.convertParamsToDestructuredObject {
function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] {
switch (functionDeclaration.kind) {
case SyntaxKind.FunctionDeclaration:
if (functionDeclaration.name) return [functionDeclaration.name];
// If the function declaration doesn't have a name, it should have a default modifier.
// We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault`
const defaultModifier = Debug.assertDefined(
findModifier(functionDeclaration, SyntaxKind.DefaultKeyword),
"Nameless function declaration should be a default export");
return [defaultModifier];
case SyntaxKind.MethodDeclaration:
return [functionDeclaration.name];
case SyntaxKind.Constructor:
const ctrKeyword = findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile())!;
const ctrKeyword = Debug.assertDefined(
findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile()),
"Constructor declaration should have constructor keyword");
if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) {
const variableDeclaration = functionDeclaration.parent.parent;
return [variableDeclaration.name, ctrKeyword];
Expand All @@ -532,13 +575,12 @@ namespace ts.refactor.convertParamsToDestructuredObject {
}

interface ValidConstructor extends ConstructorDeclaration {
parent: (ClassDeclaration & { name: Identifier }) | (ClassExpression & { parent: ValidVariableDeclaration });
parent: ClassDeclaration | (ClassExpression & { parent: ValidVariableDeclaration });
parameters: NodeArray<ValidParameterDeclaration>;
body: FunctionBody;
}

interface ValidFunction extends FunctionDeclaration {
name: Identifier;
parameters: NodeArray<ValidParameterDeclaration>;
body: FunctionBody;
}
Expand Down
15 changes: 12 additions & 3 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1664,10 +1664,15 @@ namespace ts {
return ensureScriptKind(fileName, host && host.getScriptKind && host.getScriptKind(fileName));
}

export function getSymbolTarget(symbol: Symbol): Symbol {
export function getSymbolTarget(symbol: Symbol, checker: TypeChecker): Symbol {
let next: Symbol = symbol;
while (isTransientSymbol(next) && next.target) {
next = next.target;
while (isAliasSymbol(next) || (isTransientSymbol(next) && next.target)) {
if (isTransientSymbol(next) && next.target) {
next = next.target;
}
else {
next = skipAlias(next, checker);
}
}
return next;
}
Expand All @@ -1676,6 +1681,10 @@ namespace ts {
return (symbol.flags & SymbolFlags.Transient) !== 0;
}

function isAliasSymbol(symbol: Symbol): boolean {
return (symbol.flags & SymbolFlags.Alias) !== 0;
}

export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) {
return getSymbolId(skipAlias(symbol, checker));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
tests/cases/compiler/errorMessageOnIntersectionsWithDiscriminants01.ts(8,1): error TS2322: Type 'A' is not assignable to type 'B'.
Type '{ test: true; } & { foo: 1; }' is not assignable to type 'B'.
Type '{ test: true; } & { foo: 1; }' is not assignable to type '{ test: true; } & { bar: 1; }'.
Property 'bar' is missing in type '{ test: true; } & { foo: 1; }' but required in type '{ bar: 1; }'.


==== tests/cases/compiler/errorMessageOnIntersectionsWithDiscriminants01.ts (1 errors) ====
export type Common = { test: true } | { test: false };
export type A = Common & { foo: 1 };
export type B = Common & { bar: 1 };

declare const a: A;
declare let b: B;

b = a;
~
!!! error TS2322: Type 'A' is not assignable to type 'B'.
!!! error TS2322: Type '{ test: true; } & { foo: 1; }' is not assignable to type 'B'.
!!! error TS2322: Type '{ test: true; } & { foo: 1; }' is not assignable to type '{ test: true; } & { bar: 1; }'.
!!! error TS2322: Property 'bar' is missing in type '{ test: true; } & { foo: 1; }' but required in type '{ bar: 1; }'.
!!! related TS2728 tests/cases/compiler/errorMessageOnIntersectionsWithDiscriminants01.ts:3:28: 'bar' is declared here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=== tests/cases/compiler/errorMessageOnIntersectionsWithDiscriminants01.ts ===
export type Common = { test: true } | { test: false };
>Common : Symbol(Common, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 0, 0))
>test : Symbol(test, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 0, 22))
>test : Symbol(test, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 0, 39))

export type A = Common & { foo: 1 };
>A : Symbol(A, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 0, 54))
>Common : Symbol(Common, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 0, 0))
>foo : Symbol(foo, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 1, 26))

export type B = Common & { bar: 1 };
>B : Symbol(B, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 1, 36))
>Common : Symbol(Common, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 0, 0))
>bar : Symbol(bar, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 2, 26))

declare const a: A;
>a : Symbol(a, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 4, 13))
>A : Symbol(A, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 0, 54))

declare let b: B;
>b : Symbol(b, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 5, 11))
>B : Symbol(B, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 1, 36))

b = a;
>b : Symbol(b, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 5, 11))
>a : Symbol(a, Decl(errorMessageOnIntersectionsWithDiscriminants01.ts, 4, 13))

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
=== tests/cases/compiler/errorMessageOnIntersectionsWithDiscriminants01.ts ===
export type Common = { test: true } | { test: false };
>Common : Common
>test : true
>true : true
>test : false
>false : false

export type A = Common & { foo: 1 };
>A : A
>foo : 1

export type B = Common & { bar: 1 };
>B : B
>bar : 1

declare const a: A;
>a : A

declare let b: B;
>b : B

b = a;
>b = a : A
>b : B
>a : A

Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ var r = foo(1); // ok
>1 : 1

var r2 = foo(null); // {}
>r2 : unknown
>foo(null) : unknown
>r2 : any
>foo(null) : any
>foo : <T, U extends T>(t: T) => U
>null : null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ var r8 = foo(() => { }, () => { });
>() => { } : () => void

var r9 = foo(() => { }, () => 1);
>r9 : (x: () => void) => () => 1
>foo(() => { }, () => 1) : (x: () => void) => () => 1
>r9 : (x: () => void) => () => number
>foo(() => { }, () => 1) : (x: () => void) => () => number
>foo : <T, U extends T>(t: T, t2: U) => (x: T) => U
>() => { } : () => void
>() => 1 : () => 1
>() => 1 : () => number
>1 : 1

function other<T, U extends T>() {
Expand Down
Loading

0 comments on commit 5224a6a

Please sign in to comment.