Skip to content

Should the compiler api resolve certain equivalent types to the first encountered one? #28197

@dsherret

Description

@dsherret

The question in the title is the current behaviour.

TypeScript Version: 3.2.0-dev.20181027
Relates to: #25731

The compiler API will resolve some, but not all, equivalent types to the first encountered type:

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]); // note 1, not 0 this time
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();
}

I found #25731 that says this is a design limitation. What I was wondering is in the context of the compiler API, which people aren't using only for type checking, does it make sense to have this design? When working with the compiler api, I feel like it's valuable to have distinct ts.Type objects whose text will be the name of the type alias used in the situation (ex. when someone does type MyType = string; it would be nice for it to return a MyType ts.Type object instead of the string one). Or does this just increase the complexity too much internally?

Also note that this also happens when object types are aliased...

export type Test = { prop: string; };
export type Test2 = { prop: number; };
export type AvatarSize = Test | Test2;
export type CardAvatarSize = Test | Test2;

...but not when they're inlined, which seems inconsistent?

export type AvatarSize = { prop: string; } | { prop: number; };
export type CardAvatarSize = { prop: string; } | { prop: number; };

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in codeWorking as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions