Skip to content

Fully deprecate the symbol display builder, reimplement in terms of node builder #18860

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

Merged

Conversation

weswigham
Copy link
Member

While working on the new declaration emitter, I recognized that there were certain parts of the current declaration emitter which relied on the symbol display builder. For the most part, we had heavily duplicated logic between the symbol display builder and the node builder. The PR fully removes the symbol display builder; replacing it with the node builder and a printer, after upgrading our emitter to do some of its writes in the more granular fashion that the symbol display writer used to expect, and altering the node builder to be capable of the little bit of extra reporting the declaration emitter expects.

This enables me to use the node builder (as one would expect) in the new declaration emitter in a similar fashion to how it is now used in the current declaration emitter.

While I've tried to keep the baseline changes to the absolute minimum, there are a few correctness changes, and a few whitespace changes. The correctness changes are for mapped types - previously when the constraint was written, it would write the declared constraint, rather than the instantiated constraint (which was noticeable in declaration emit baselines, so this has now been fixed). The whitespace changes have to do with the emitter - parameter, type parameter, and type argument lists were marked as both SingleLine and Indented - causing objects written across multiple lines to have way more indents than they should - these are no longer marked as Indented, and so no longer randomly add indentation (even though they should never add newlines on their own). This improved some of our js baselines to actually match the input, which is a good thing (in addition to making the declaration output more closely match old output). Other minor changes are that mapped types are written on a single line when synthesized, rather than on multiple lines, and that the declaration emitter more aggressively uses the typeof operator where possible (as before a small bug in the symbol display builder was preventing it from finding some local aliases).

As the SymbolDisplayBuilder (and SymbolWriter, which has been subsumed into EmitTextWriter) was part of our public API, this is a breaking change - however new XToString APIs exist which take a writer and can be used to polyfill the functionality.

@weswigham weswigham force-pushed the couple-node-builder-with-symbol-tracker branch from ec3f036 to f5c79d5 Compare November 11, 2017 01:25
return result;
}
if (flags & TypeFormatFlags.NoTruncation) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we align the values between these two enums and just use a bitmask?

@@ -286,16 +286,21 @@ namespace ts {
let tempFlags: TempFlags; // TempFlags for the current name generation scope.
let writer: EmitTextWriter;
let ownWriter: EmitTextWriter;
let write = writeBase;
let pendingSemicolon = false;
const syntheticParent: TextRange = { pos: -1, end: -1 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly adds polymorphism because Node sets id and kind before pos and end, resulting in an incompatible hidden class. Consider moving pos and end up in Node, or give syntheticParent an id and a kind.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved pos and end before the other fields - they were already first in the services constructor implementation for Node, so I think this only changes tsc.

@@ -887,8 +911,8 @@ namespace ts {
//

function emitIdentifier(node: Identifier) {
write(getTextOfNode(node, /*includeTrivia*/ false));
emitTypeArguments(node, node.typeArguments);
(node.symbol ? writeSymbol : write)(getTextOfNode(node, /*includeTrivia*/ false), node.symbol);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ternary makes this harder to read.

}

function emitParameter(node: ParameterDeclaration) {
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
emitIfPresent(node.dotDotDotToken);
if (node.name) {
const savedWrite = write;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use this pattern in enough places to consider a helper to do this: emitWithWriter(node.name, writeParameter)

function emitNodeWithWriter(node: Node, writer: typeof write) {
  const savedWrite = write;
  write = writer;
  emit(node);
  write = savedWrite;
}

emit(node.name);
emitTypeParameters(node, node.typeParameters);
write(" = ");
writeSpace(); writePunctuation("="); writeSpace();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate lines

typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation));
const typeParameterParts = mapToDisplayParts(writer => {
if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) {
const printer = createPrinter({ removeComments: true });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, you only need one printer.

const displayParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation));
const displayParts = mapToDisplayParts(writer => {
const printer = createPrinter({ removeComments: true });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, Printer is designed to be reused when passed the same set of initial arguments. No sense in recreating it all the time.

getColumn: () => 0,
getLine: () => 0,
isAtStartOfLine: () => false,
rawWrite: noop,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be noop or should it raise an error when called?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed it to raise an error, since rawWrite is only used to print comments, and the display part writer should only be used when comment printing is disabled.

@@ -2651,8 +2662,13 @@ namespace ts {
getTypeAtLocation(node: Node): Type;
getTypeFromTypeNode(node: TypeNode): Type;
signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string;
/* @internal */ signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind, writer?: EmitTextWriter): string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than overloading these, why not have:

  /* @internal */ writeSignature(writer: EmitTextWriter, signature: Signature, ...): void;
  /* @internal */ writeType(writer: EmitTextWriter, type: Type, ...): void;
  /* @internal */ writeSymbol(writer: EmitTextWriter, symbol: Symbol, ...): void;
  /* @internal */ writeTypePredicate(writer: EmitTextWriter, predicate: TypePredicate, ...): void;

That is similar to how Printer splits up internal and public members.

@@ -2841,6 +2864,9 @@ namespace ts {
// eg. module m { export class c { } } import x = m.c;
// When this flag is specified m.c will be used to refer to the class instead of alias symbol x
UseOnlyExternalAliasing = 0x00000002,

// Build symbol name using property accesses and element accesses, instead of an entity name
AllowAnyNodeKind = 0x00000004,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name AllowAnyNodeKind doesn't seem to align with the intended use described in the comment. Perhaps a better description or a more descriptive name would be better here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the description

@weswigham weswigham force-pushed the couple-node-builder-with-symbol-tracker branch from 6862e50 to 3e18178 Compare January 3, 2018 22:07
@weswigham
Copy link
Member Author

@rbuckton I've finally gotten around to syncing this and implementing your comments. Is there anything else you'd like to see?

@mhegazy
Copy link
Contributor

mhegazy commented Jan 8, 2018

@weswigham can you please refresh, sync with @rbuckton and merge

@weswigham weswigham merged commit 76d9524 into microsoft:master Jan 16, 2018
@weswigham weswigham deleted the couple-node-builder-with-symbol-tracker branch February 26, 2018 23:55
@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants