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

lint for used-but-not-declared and declared-but-not-used variables in algorithms #483

Merged
merged 6 commits into from
Sep 8, 2022

Conversation

bakkot
Copy link
Contributor

@bakkot bakkot commented Sep 5, 2022

Based on #482. Fixes #129.

Given tc39/ecma262#2896 and tc39/ecma262@5f759fa, this passes on ecma262 except for a few genuinely unused declarations, which I'll fix as part of merging this.

This is pretty conservative (e.g. it assumes any variable referred to in a previous paragraph in the clause is defined, to handle stuff like this), but seems to work well. In particular it handles abstract closures correctly (variables must be captured from the outer scope, captures count as using variables from the outer scope).

report: Reporter
) {
if (containingAlgorithm.hasAttribute('replaces-step')) {
// TODO someday lint these by doing the rewrite (conceptually)
Copy link
Member

Choose a reason for hiding this comment

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

aww, I was looking forward to seeing how you did this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not that hard - you know exactly the step being replaced, so you can swap out that step in the original and re-run this algorithm, discarding any warnings which don't apply to the replacement section - but it just doesn't come up enough to be worth worrying about.

if (isVariable(loopVar)) {
loopVars.add(loopVar);

scope.declare(loopVar.contents[0].contents, loopVar, 'loop variable');
Copy link
Member

Choose a reason for hiding this comment

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

break here so you don't try to declare the loop target or anything mentioned in the guard

Copy link
Member

Choose a reason for hiding this comment

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

We should also probably have a test for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't want to declare anything else, but we do want to count references on the rest of the line as uses, and just putting a continue here would skip that. What test are you imagining continueing here would fix?

Copy link
Member

Choose a reason for hiding this comment

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

1. For each integer _a_ of _b_ such that _c_ relates to _a_ in some way, do

Copy link
Contributor Author

@bakkot bakkot Sep 7, 2022

Choose a reason for hiding this comment

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

That case is guarded explicitly:

if (
// "of"/"in" guard is to distinguish "an integer X such that" from "an integer X in Y such that" - latter should not declare Y
(isSuchThat && cur.name === 'text' && !/(?: of | in )/.test(cur.contents)) ||
(isBe && cur.name === 'text' && /\blet (?:each of )?$/i.test(cur.contents))
) {
.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a test for it too:

it('"there exists _x_ in _y_ such that" declares _x_ but not _y_"', async () => {
await assertLint(
positioned`
<emu-alg>
1. If there exists an integer _x_ in ${M}_y_ such that _x_ < 10, return *true*.
1. Return *false*.
</emu-alg>
`,
{
ruleId: 'use-before-def',
nodeType: 'emu-alg',
message: 'could not find a preceding declaration for "y"',
}
);
});

Though not using the "for each" style; I can add one for that case, if you want.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if (isSuchThat || isBe) {
const varsDeclaredHere = [part];
let varIndex = i - 1;
// walk backwards collecting this comma/'and' seperated list of variables
Copy link
Member

Choose a reason for hiding this comment

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

I'd kind of rather get rid of these. We can leave this support for now, but can you open an issue on 262 that lists these cases so we can review them for phrasing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure about an issue on 262, but here's a list for you.

In Temporal there is also ParseTimeZoneOffsetString step 3.

Copy link
Contributor

Choose a reason for hiding this comment

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

(Note that PR #2288 takes care of Module Record GetBindingValue step 3.a.)

);
});

it('"such that" counts as a declaration"', async () => {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
it('"such that" counts as a declaration"', async () => {
it('"such that" counts as a use"', async () => {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, it's a declaration. For example

While _finalizationRegistry_.[[Cells]] contains a Record _cell_ such that _cell_.[[WeakRefTarget]] is ~empty~, [...]

declares _cell_.

Base automatically changed from lint-step-attributes to main September 8, 2022 02:51
@bakkot bakkot merged commit 6a8e5cd into main Sep 8, 2022
@bakkot bakkot deleted the lint-variables branch September 8, 2022 02:56
@bakkot bakkot mentioned this pull request Oct 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

warn when variables appear only once
3 participants