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

Emit declarations using alias chains #56100

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 36 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5591,7 +5591,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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);
const ref = getAliasChainForSymbolInContainer(resolvedModule, symbol);
if (!ref) continue;
results = append(results, resolvedModule);
}
Expand All @@ -5608,7 +5608,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
for (const file of otherFiles) {
if (!isExternalModule(file)) continue;
const sym = getSymbolOfDeclaration(file);
const ref = getAliasForSymbolInContainer(sym, symbol);
const ref = getAliasChainForSymbolInContainer(sym, symbol);
if (!ref) continue;
results = append(results, sym);
}
Expand Down Expand Up @@ -5647,7 +5647,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!length(candidates)) {
return undefined;
}
const containers = mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined);
const containers = mapDefined(candidates, candidate => getAliasChainForSymbolInContainer(candidate, symbol) ? candidate : undefined);

let bestContainers: Symbol[] = [];
let alternativeContainers: Symbol[] = [];
Expand Down Expand Up @@ -5713,27 +5713,38 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return exported && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined;
}

function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) {
function getAliasChainForSymbolInContainer(container: Symbol, symbol: Symbol) {
if (container === getParentOfSymbol(symbol)) {
// fast path, `symbol` is either already the alias or isn't aliased
return symbol;
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 [container];
}
return forEachEntry(exports, exported => {
if (getSymbolIfSameReference(exported, symbol)) {
return exported;
return getAliasChainRecursively(container);

function getAliasChainRecursively(container: Symbol): Symbol[] | undefined {
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];
}
if (exported.flags & SymbolFlags.Alias) {
const aliasChain = getAliasChainRecursively(resolveAlias(exported));
if (aliasChain) {
aliasChain.unshift(exported);
return aliasChain;
}
}
});
}
}

/**
Expand Down Expand Up @@ -7984,7 +7995,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
accessibleSymbolChain = parentChain;
break;
}
accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]);
if (accessibleSymbolChain) {
accessibleSymbolChain = parentChain.concat(accessibleSymbolChain);
break;
}
const aliasChain = getAliasChainForSymbolInContainer(parent, symbol);
if (aliasChain) {
accessibleSymbolChain = parentChain.concat(aliasChain);
break;
}
accessibleSymbolChain = parentChain.concat(symbol);
break;
}
}
Expand Down
82 changes: 82 additions & 0 deletions tests/baselines/reference/declarationEmitUsingAliasSymbolChain1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//// [tests/cases/compiler/declarationEmitUsingAliasSymbolChain1.ts] ////

//// [Data.d.ts]
import type * as Types from "./Types.js";
import type * as Equal from "./Equal.js";

export type Data<A extends Readonly<Record<string, any>> | ReadonlyArray<any>> =
Readonly<A> & Equal.Equal;

export declare const TaggedClass: <Key extends string>(
tag: Key,
) => new <A extends Record<string, any>>(
args: Types.Equals<Omit<A, keyof Equal.Equal>, {}> extends true
? void
: Omit<A, keyof Equal.Equal>,
) => Data<
A & {
_tag: Key;
}
>;

//// [Equal.d.ts]
import * as Hash from "./Hash.js";

export declare const symbol: unique symbol;

export interface Equal extends Hash.Hash {
[symbol](that: Equal): boolean;
}

//// [Hash.d.ts]
export declare const symbol: unique symbol;

export interface Hash {
[symbol](): number;
}

//// [Types.d.ts]
export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
T,
>() => T extends Y ? 1 : 2
? true
: false;

//// [index.d.ts]
export * as Data from "./Data.js";
export * as Equal from "./Equal.js";
export * as Types from "./Types.js";

//// [effect.cjs.d.ts]
export * from "./declarations/src/index";

//// [package.json]
{
"name": "effect",
"exports": {
".": "./dist/effect.cjs.js"
}
}

//// [index.ts]
import { Data } from "effect";
export class Foo extends Data.TaggedClass("Foo")<{}> {}

//// [index.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Foo = void 0;
const effect_1 = require("effect");
class Foo extends effect_1.Data.TaggedClass("Foo") {
}
exports.Foo = Foo;


//// [index.d.ts]
import { Data } from "effect";
declare const Foo_base: new <A extends Record<string, any>>(args: import("effect").Types.Equals<Omit<A, keyof import("effect").Equal.Equal>, {}> extends true ? void : Omit<A, keyof import("effect").Equal.Equal>) => Data.Data<A & {
_tag: "Foo";
}>;
export declare class Foo extends Foo_base<{}> {
}
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//// [tests/cases/compiler/declarationEmitUsingAliasSymbolChain1.ts] ////

=== node_modules/effect/dist/declarations/src/Data.d.ts ===
import type * as Types from "./Types.js";
>Types : Symbol(Types, Decl(Data.d.ts, 0, 11))

import type * as Equal from "./Equal.js";
>Equal : Symbol(Equal, Decl(Data.d.ts, 1, 11))

export type Data<A extends Readonly<Record<string, any>> | ReadonlyArray<any>> =
>Data : Symbol(Data, Decl(Data.d.ts, 1, 41))
>A : Symbol(A, Decl(Data.d.ts, 3, 17))
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2016.array.include.d.ts, --, --) ... and 3 more)

Readonly<A> & Equal.Equal;
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
>A : Symbol(A, Decl(Data.d.ts, 3, 17))
>Equal : Symbol(Equal, Decl(Data.d.ts, 1, 11))
>Equal : Symbol(Equal.Equal, Decl(Equal.d.ts, 2, 43))

export declare const TaggedClass: <Key extends string>(
>TaggedClass : Symbol(TaggedClass, Decl(Data.d.ts, 6, 20))
>Key : Symbol(Key, Decl(Data.d.ts, 6, 35))

tag: Key,
>tag : Symbol(tag, Decl(Data.d.ts, 6, 55))
>Key : Symbol(Key, Decl(Data.d.ts, 6, 35))

) => new <A extends Record<string, any>>(
>A : Symbol(A, Decl(Data.d.ts, 8, 10))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

args: Types.Equals<Omit<A, keyof Equal.Equal>, {}> extends true
>args : Symbol(args, Decl(Data.d.ts, 8, 41))
>Types : Symbol(Types, Decl(Data.d.ts, 0, 11))
>Equals : Symbol(Types.Equals, Decl(Types.d.ts, 0, 0))
>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --))
>A : Symbol(A, Decl(Data.d.ts, 8, 10))
>Equal : Symbol(Equal, Decl(Data.d.ts, 1, 11))
>Equal : Symbol(Equal.Equal, Decl(Equal.d.ts, 2, 43))

? void
: Omit<A, keyof Equal.Equal>,
>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --))
>A : Symbol(A, Decl(Data.d.ts, 8, 10))
>Equal : Symbol(Equal, Decl(Data.d.ts, 1, 11))
>Equal : Symbol(Equal.Equal, Decl(Equal.d.ts, 2, 43))

) => Data<
>Data : Symbol(Data, Decl(Data.d.ts, 1, 41))

A & {
>A : Symbol(A, Decl(Data.d.ts, 8, 10))

_tag: Key;
>_tag : Symbol(_tag, Decl(Data.d.ts, 13, 7))
>Key : Symbol(Key, Decl(Data.d.ts, 6, 35))
}
>;

=== node_modules/effect/dist/declarations/src/Equal.d.ts ===
import * as Hash from "./Hash.js";
>Hash : Symbol(Hash, Decl(Equal.d.ts, 0, 6))

export declare const symbol: unique symbol;
>symbol : Symbol(symbol, Decl(Equal.d.ts, 2, 20))

export interface Equal extends Hash.Hash {
>Equal : Symbol(Equal, Decl(Equal.d.ts, 2, 43))
>Hash.Hash : Symbol(Hash.Hash, Decl(Hash.d.ts, 0, 43))
>Hash : Symbol(Hash, Decl(Equal.d.ts, 0, 6))
>Hash : Symbol(Hash.Hash, Decl(Hash.d.ts, 0, 43))

[symbol](that: Equal): boolean;
>[symbol] : Symbol(Equal[symbol], Decl(Equal.d.ts, 4, 42))
>symbol : Symbol(symbol, Decl(Equal.d.ts, 2, 20))
>that : Symbol(that, Decl(Equal.d.ts, 5, 11))
>Equal : Symbol(Equal, Decl(Equal.d.ts, 2, 43))
}

=== node_modules/effect/dist/declarations/src/Hash.d.ts ===
export declare const symbol: unique symbol;
>symbol : Symbol(symbol, Decl(Hash.d.ts, 0, 20))

export interface Hash {
>Hash : Symbol(Hash, Decl(Hash.d.ts, 0, 43))

[symbol](): number;
>[symbol] : Symbol(Hash[symbol], Decl(Hash.d.ts, 2, 23))
>symbol : Symbol(symbol, Decl(Hash.d.ts, 0, 20))
}

=== node_modules/effect/dist/declarations/src/Types.d.ts ===
export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
>Equals : Symbol(Equals, Decl(Types.d.ts, 0, 0))
>X : Symbol(X, Decl(Types.d.ts, 0, 19))
>Y : Symbol(Y, Decl(Types.d.ts, 0, 21))
>T : Symbol(T, Decl(Types.d.ts, 0, 29))
>T : Symbol(T, Decl(Types.d.ts, 0, 29))
>X : Symbol(X, Decl(Types.d.ts, 0, 19))

T,
>T : Symbol(T, Decl(Types.d.ts, 0, 67))

>() => T extends Y ? 1 : 2
>T : Symbol(T, Decl(Types.d.ts, 0, 67))
>Y : Symbol(Y, Decl(Types.d.ts, 0, 21))

? true
: false;

=== node_modules/effect/dist/declarations/src/index.d.ts ===
export * as Data from "./Data.js";
>Data : Symbol(Data, Decl(index.d.ts, 0, 6))

export * as Equal from "./Equal.js";
>Equal : Symbol(Equal, Decl(index.d.ts, 1, 6))

export * as Types from "./Types.js";
>Types : Symbol(Types, Decl(index.d.ts, 2, 6))

=== node_modules/effect/dist/effect.cjs.d.ts ===

export * from "./declarations/src/index";

=== src/index.ts ===
import { Data } from "effect";
>Data : Symbol(Data, Decl(index.ts, 0, 8))

export class Foo extends Data.TaggedClass("Foo")<{}> {}
>Foo : Symbol(Foo, Decl(index.ts, 0, 30))
>Data.TaggedClass : Symbol(Data.TaggedClass, Decl(Data.d.ts, 6, 20))
>Data : Symbol(Data, Decl(index.ts, 0, 8))
>TaggedClass : Symbol(Data.TaggedClass, Decl(Data.d.ts, 6, 20))

Loading