Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why does the ts type checker return structurally equivalent types as the first one requested? #480

Closed
dsherret opened this issue Oct 27, 2018 · 4 comments
Labels

Comments

@dsherret
Copy link
Owner

dsherret commented Oct 27, 2018

From #450. For example:

const sourceFile = project.createSourceFile("file.ts", `
export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
export type CardAvatarSize = 'sm' | 'md' | 'lg' | 'xl';

const v1: AvatarSize;
const v2: CardAvatarSize;
`);

// both output "AvatarSize"
console.log(sourceFile.getVariableDeclarationOrThrow("v1").getType().getText());
console.log(sourceFile.getVariableDeclarationOrThrow("v2").getType().getText());

I bet this is by design, but logging it anyway.

@dsherret
Copy link
Owner Author

dsherret commented Oct 27, 2018

Here's a compiler api example:

import * as ts from "typescript";

const testFilePath = "/file.ts";
const testFileText = `
export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
export type CardAvatarSize = 'sm' | 'md' | 'lg' | 'xl';

let v1: AvatarSize, v2: CardAvatarSize;
`;

// common setup
const testSourceFile = ts.createSourceFile(testFilePath, testFileText, ts.ScriptTarget.Latest);
const variableStatement = testSourceFile.statements.find(ts.isVariableStatement)!;
const variableDeclarations = variableStatement.declarationList.declarations;

// outputs: "AvatarSize", "AvatarSize"
const typeChecker1 = getTypeChecker();
logTypeText(typeChecker1, variableDeclarations[0]);
logTypeText(typeChecker1, variableDeclarations[1]);

// outputs: "CardAvatarSize", "CardAvatarSize"
const typeChecker2 = getTypeChecker();
logTypeText(typeChecker2, variableDeclarations[1]);
logTypeText(typeChecker2, variableDeclarations[0]);

function logTypeText(typeChecker: ts.TypeChecker, declaration: ts.VariableDeclaration) {
    const type = typeChecker.getTypeAtLocation(declaration.type!);
    console.log(typeChecker.typeToString(type));
}

function getTypeChecker() {
    const options: ts.CompilerOptions = { target: ts.ScriptTarget.ES5 };
    const host: ts.CompilerHost = {
        fileExists: filePath => filePath === testFilePath,
        directoryExists: dirPath => dirPath === "/",
        getCurrentDirectory: () => "/",
        getDirectories: () => [],
        getCanonicalFileName: fileName => fileName,
        getNewLine: () => "\n",
        getDefaultLibFileName: () => "",
        getSourceFile: filePath => filePath === testFilePath ? testSourceFile : undefined,
        readFile: filePath => filePath === testFilePath ? testFileText : undefined,
        useCaseSensitiveFileNames: () => true,
        writeFile: () => {}
    };
    return ts.createProgram({
        options,
        rootNames: [testFilePath],
        host
    }).getTypeChecker();
}

@dsherret
Copy link
Owner Author

I think this is by design. For example, when changing it to this:

export type AvatarSize = string;
export type CardAvatarSize = string;

It will always output string. Closing...

@dsherret
Copy link
Owner Author

Note the following behaviour of the compiler:

// test.ts
export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
export type CardAvatarSize = 'sm' | 'md' | 'lg' | 'xl';

export class Test {
    method1() {
        return "sm" as AvatarSize;
    }
    method2() {
        return "sm" as CardAvatarSize;
    }
}

Outputs the following declaration file:

// test.d.ts
export declare type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
export declare type CardAvatarSize = 'sm' | 'md' | 'lg' | 'xl';
export declare class Test {
    method1(): AvatarSize;
    method2(): AvatarSize;
}

I'm going to reopen this issue for now.

@dsherret dsherret reopened this Oct 28, 2018
@dsherret
Copy link
Owner Author

dsherret commented Nov 3, 2018

Response from the compiler team in microsoft/TypeScript#28197:

For performance reasons, we intern types where possible (this way we avoid duplicating work for equivalent types). We do not currently intern anonymous object types, though we've experimented with it before. Unfortunately, interning object types has the side effect of breaking go to definition on the interned types; so we didn't pull it in. The specific types we intern today are indexed accesses, unions, and intersections (also reverse mapped types, but only inference can produce those). This is a tradeoff - origin information is lost on interned types; but we do avoid quite a bit of work most of the time.

@dsherret dsherret closed this as completed Nov 3, 2018
@dsherret dsherret reopened this Apr 3, 2021
@dsherret dsherret closed this as completed Apr 3, 2021
Repository owner locked and limited conversation to collaborators Apr 3, 2021
Repository owner unlocked this conversation Apr 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant