Skip to content

Refactor emit substitution into transform #42676

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
merged 8 commits into from
Feb 26, 2021
Merged
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
115 changes: 78 additions & 37 deletions scripts/perf-result-post.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,86 @@
// Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally
const { Octokit } = require("@octokit/rest");
const fs = require("fs");
const ado = require("azure-devops-node-api");
const { default: fetch } = require("node-fetch");

const requester = process.env.requesting_user;
const source = process.env.source_issue;
const postedComment = process.env.status_comment;
console.log(`Loading fragment from ${process.argv[3]}...`);
const outputTableText = fs.readFileSync(process.argv[3], { encoding: "utf8" });
console.log(`Fragment contents:
${outputTableText}`);

const gh = new Octokit({
auth: process.argv[2]
});
gh.issues.createComment({
issue_number: +source,
owner: "Microsoft",
repo: "TypeScript",
body: `@${requester}
The results of the perf run you requested are in!
<details><summary> Here they are:</summary><p>
${outputTableText}
</p></details>`
}).then(async data => {
console.log(`Results posted!`);
const newCommentUrl = data.data.html_url;
const comment = await gh.issues.getComment({
owner: "Microsoft",
repo: "TypeScript",
comment_id: +postedComment
});
const newBody = `${comment.data.body}

Update: [The results are in!](${newCommentUrl})`;
return await gh.issues.updateComment({
owner: "Microsoft",
repo: "TypeScript",
comment_id: +postedComment,
body: newBody
});
}).catch(e => {
async function main() {
const source = process.env.SOURCE_ISSUE;
if (!source) throw new Error("SOURCE_ISSUE environment variable not set.");

const requester = process.env.REQUESTING_USER;
if (!requester) throw new Error("REQUESTING_USER environment variable not set.");

const buildId = process.env.BUILD_BUILDID;
if (!requester) throw new Error("BUILD_BUILDID environment variable not set.");

const postedComment = process.env.STATUS_COMMENT;
if (!postedComment) throw new Error("STATUS_COMMENT environment variable not set.");

const [auth, fragment, includeArtifact] = process.argv.slice(2);
if (!auth) throw new Error("First argument must be a GitHub auth token.");
if (!fragment) throw new Error("Second argument must be a path to an HTML fragment.");

const gh = new Octokit({ auth });
try {
console.log(`Loading fragment from ${fragment}...`);
const outputTableText = fs.readFileSync(fragment, { encoding: "utf8" });
console.log(`Fragment contents:\n${outputTableText}`);

let benchmarkText = "";
if (includeArtifact === "--include-artifact") {
// post a link to the benchmark file
const cli = new ado.WebApi("https://typescript.visualstudio.com/defaultcollection", ado.getHandlerFromToken("")); // Empty token, anon auth
const build = await cli.getBuildApi();
const artifact = await build.getArtifact("typescript", +buildId, "benchmark");
const updatedUrl = new URL(artifact.resource.url);
updatedUrl.search = `artifactName=benchmark&fileId=${artifact.resource.data}&fileName=manifest`;
const resp = await (await fetch(`${updatedUrl}`)).json();
for (const file of resp.items) {
if (/[\\/]linux\.benchmark$/.test(file.path)) {
const benchmarkUrl = new URL(artifact.resource.url);
benchmarkUrl.search = `artifactName=benchmark&fileId=${file.blob.id}&fileName=linux.benchmark`;
benchmarkText = `\n<details><summary>Developer Information:</summary><p><a href="${benchmarkUrl.href}">Download Benchmark</a></p></details>\n`;
break;
}
}
}

const data = await gh.issues.createComment({
issue_number: +source,
owner: "Microsoft",
repo: "TypeScript",
body: `@${requester}\nThe results of the perf run you requested are in!\n<details><summary> Here they are:</summary><p>\n${outputTableText}\n</p>${benchmarkText}</details>`
});

console.log(`Results posted!`);
const newCommentUrl = data.data.html_url;
const comment = await gh.issues.getComment({
owner: "Microsoft",
repo: "TypeScript",
comment_id: +postedComment
});
const newBody = `${comment.data.body}\n\nUpdate: [The results are in!](${newCommentUrl})`;
await gh.issues.updateComment({
owner: "Microsoft",
repo: "TypeScript",
comment_id: +postedComment,
body: newBody
});
}
catch (e) {
const gh = new Octokit({ auth });
await gh.issues.createComment({
issue_number: +source,
owner: "Microsoft",
repo: "TypeScript",
body: `Hey @${requester}, something went wrong when publishing results. ([You can check the log here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${buildId}&_a=summary)).`
});
}
}

main().catch(e => {
console.error(e);
process.exit(1);
});
201 changes: 90 additions & 111 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ namespace ts {

const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable };
const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable };
const bindBinaryExpressionFlow = createBindBinaryExpressionFlow();

/**
* Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file)
Expand Down Expand Up @@ -1500,132 +1501,110 @@ namespace ts {
bindAssignmentTargetFlow(node.left);
}

const enum BindBinaryExpressionFlowState {
BindThenBindChildren,
MaybeBindLeft,
BindToken,
BindRight,
FinishBind
}

function bindBinaryExpressionFlow(node: BinaryExpression) {
const workStacks: {
expr: BinaryExpression[],
state: BindBinaryExpressionFlowState[],
inStrictMode: (boolean | undefined)[],
parent: (Node | undefined)[],
} = {
expr: [node],
state: [BindBinaryExpressionFlowState.MaybeBindLeft],
inStrictMode: [undefined],
parent: [undefined],
};
let stackIndex = 0;
while (stackIndex >= 0) {
node = workStacks.expr[stackIndex];
switch (workStacks.state[stackIndex]) {
case BindBinaryExpressionFlowState.BindThenBindChildren: {
// This state is used only when recuring, to emulate the work that `bind` does before
// reaching `bindChildren`. A normal call to `bindBinaryExpressionFlow` will already have done this work.
setParent(node, parent);
const saveInStrictMode = inStrictMode;
bindWorker(node);
const saveParent = parent;
parent = node;

advanceState(BindBinaryExpressionFlowState.MaybeBindLeft, saveInStrictMode, saveParent);
break;
}
case BindBinaryExpressionFlowState.MaybeBindLeft: {
const operator = node.operatorToken.kind;
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken ||
isLogicalOrCoalescingAssignmentOperator(operator)) {
if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createBranchLabel();
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);
currentFlow = finishFlowLabel(postExpressionLabel);
}
else {
bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!);
}
completeNode();
}
else {
advanceState(BindBinaryExpressionFlowState.BindToken);
maybeBind(node.left);
}
break;
}
case BindBinaryExpressionFlowState.BindToken: {
if (node.operatorToken.kind === SyntaxKind.CommaToken) {
maybeBindExpressionFlowIfCall(node.left);
}
advanceState(BindBinaryExpressionFlowState.BindRight);
maybeBind(node.operatorToken);
break;
}
case BindBinaryExpressionFlowState.BindRight: {
advanceState(BindBinaryExpressionFlowState.FinishBind);
maybeBind(node.right);
break;
function createBindBinaryExpressionFlow() {
interface WorkArea {
stackIndex: number;
skip: boolean;
inStrictModeStack: (boolean | undefined)[];
parentStack: (Node | undefined)[];
}

return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined);

function onEnter(node: BinaryExpression, state: WorkArea | undefined) {
if (state) {
state.stackIndex++;
// Emulate the work that `bind` does before reaching `bindChildren`. A normal call to
// `bindBinaryExpressionFlow` will already have done this work.
setParent(node, parent);
const saveInStrictMode = inStrictMode;
bindWorker(node);
const saveParent = parent;
parent = node;
state.skip = false;
state.inStrictModeStack[state.stackIndex] = saveInStrictMode;
state.parentStack[state.stackIndex] = saveParent;
}
else {
state = {
stackIndex: 0,
skip: false,
inStrictModeStack: [undefined],
parentStack: [undefined]
};
}
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
const operator = node.operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken ||
operator === SyntaxKind.BarBarToken ||
operator === SyntaxKind.QuestionQuestionToken ||
isLogicalOrCoalescingAssignmentOperator(operator)) {
if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createBranchLabel();
bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel);
currentFlow = finishFlowLabel(postExpressionLabel);
}
case BindBinaryExpressionFlowState.FinishBind: {
const operator = node.operatorToken.kind;
if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) {
bindAssignmentTargetFlow(node.left);
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) {
const elementAccess = <ElementAccessExpression>node.left;
if (isNarrowableOperand(elementAccess.expression)) {
currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node);
}
}
}
completeNode();
break;
else {
bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!);
}
default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for bindBinaryExpressionFlow`);
state.skip = true;
}
return state;
}

/**
* Note that `advanceState` sets the _current_ head state, and that `maybeBind` potentially pushes on a new
* head state; so `advanceState` must be called before any `maybeBind` during a state's execution.
*/
function advanceState(state: BindBinaryExpressionFlowState, isInStrictMode?: boolean, parent?: Node) {
workStacks.state[stackIndex] = state;
if (isInStrictMode !== undefined) {
workStacks.inStrictMode[stackIndex] = isInStrictMode;
function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) {
if (!state.skip) {
return maybeBind(left);
}
if (parent !== undefined) {
workStacks.parent[stackIndex] = parent;
}

function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) {
if (!state.skip) {
if (operatorToken.kind === SyntaxKind.CommaToken) {
maybeBindExpressionFlowIfCall(node.left);
}
bind(operatorToken);
}
}

function completeNode() {
if (workStacks.inStrictMode[stackIndex] !== undefined) {
inStrictMode = workStacks.inStrictMode[stackIndex]!;
parent = workStacks.parent[stackIndex]!;
function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) {
if (!state.skip) {
return maybeBind(right);
}
stackIndex--;
}

/**
* If `node` is a BinaryExpression, adds it to the local work stack, otherwise recursively binds it
*/
function onExit(node: BinaryExpression, state: WorkArea) {
if (!state.skip) {
const operator = node.operatorToken.kind;
if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) {
bindAssignmentTargetFlow(node.left);
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) {
const elementAccess = <ElementAccessExpression>node.left;
if (isNarrowableOperand(elementAccess.expression)) {
currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node);
}
}
}
}
const savedInStrictMode = state.inStrictModeStack[state.stackIndex];
const savedParent = state.parentStack[state.stackIndex];
if (savedInStrictMode !== undefined) {
inStrictMode = savedInStrictMode;
}
if (savedParent !== undefined) {
parent = savedParent;
}
state.skip = false;
state.stackIndex--;
}

function maybeBind(node: Node) {
if (node && isBinaryExpression(node) && !isDestructuringAssignment(node)) {
stackIndex++;
workStacks.expr[stackIndex] = node;
workStacks.state[stackIndex] = BindBinaryExpressionFlowState.BindThenBindChildren;
workStacks.inStrictMode[stackIndex] = undefined;
workStacks.parent[stackIndex] = undefined;
}
else {
bind(node);
return node;
}
bind(node);
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/compiler/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,14 @@ namespace ts {
}
}

/**
* Asserts a value has the specified type in typespace only (does not perform a runtime assertion).
* This is useful in cases where we switch on `node.kind` and can be reasonably sure the type is accurate, and
* as a result can reduce the number of unnecessary casts.
*/
export function type<T>(value: unknown): asserts value is T;
export function type(_value: unknown) { }

export function getFunctionName(func: AnyFunction) {
if (typeof func !== "function") {
return "";
Expand Down
Loading