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

Rearrange name poisoning logic to do a little less work. #4766

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from

Conversation

zygoloid
Copy link
Contributor

@zygoloid zygoloid commented Jan 7, 2025

Insert the poison at the same time we do the name lookup to avoid doing
two hash table lookups into each scope. This adds a bit of complication
because import logic now needs to cope with importing a name that is
already poisoned, but the complexity seems worthwhile to reduce the
number of name lookups performed.

This incidentally fixes a bug where we wouldn't poison any name scopes
if we found the name in an enclosing lexical scope, leading to one extra
diagnostic in existing tests.

Part of #4622

Insert the poison at the same time we do the name lookup to avoid doing
two hash table lookups into each scope. This adds a bit of complication
because import logic now needs to cope with importing a name that is
already poisoned, but the complexity seems worthwhile to reduce the
number of name lookups performed.

This incidentally fixes a bug where we wouldn't poison any name scopes
if we found the name in an enclosing lexical scope, leading to one extra
diagnostic in existing tests.
@zygoloid zygoloid requested a review from bricknerb January 7, 2025 20:10
@github-actions github-actions bot requested a review from jonmeow January 7, 2025 20:11
Copy link
Contributor

@bricknerb bricknerb left a comment

Choose a reason for hiding this comment

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

Thanks for doing this!
It's definitely more optimized.
I do not fully understand the behavior change though.
What I thought we were trying to do before is when lookup was successful, poison the name in all the scopes between where the name was found and where we started looking.
From the test change, it seems that now we poison a name even when the lookup is not successful.
Is this what we want?
See #4774

EXPECT_THAT(name_scope.entries(),
ElementsAre(NameScopeEntryEquals(
NameScope::Entry({.name_id = poison1,
.inst_id = InstId::PoisonedName,
.access_kind = AccessKind::Public}))));

NameId poison2(++id);
name_scope.AddPoison(poison2);
EXPECT_EQ(name_scope.LookupOrPoison(poison2), std::nullopt);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should add coverage for cases where LookupOrPoison() doesn't return std::nullopt.

CARBON_CHECK(result.is_inserted(), "Failed to add required name: {0}",
name_entry.name_id);
if (!result.is_inserted()) {
// A required name can overwrite poison.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this tested in the unit tests?

@jonmeow
Copy link
Contributor

jonmeow commented Jan 8, 2025

Can you add some context for why #4622 is mentioned in the PR description?

Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

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

Are you thinking about something like SFINAE for templates to get name poisoning to be an error this way?

// If `for_decl_name` is false, then this is a regular name lookup, and the
// name will be poisoned if not found so that later lookups will fail; a
// poisoned name will be treated as if it is not declared. Otherwise, this is
// a lookup for a name being declared, so the name will not be poisoned, but
Copy link
Contributor

Choose a reason for hiding this comment

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

"for_decl_name" making me stumble a little, partly with "is" versus "for". Noting the way you describe it here and how it's called, had you considered something like "is_in_decl" to match "LookupNameInDecl", or "is_declaring", something like that?

Comment on lines +63 to +64
// Adds a new name known to not exist. The new entry may not be poisoned. This
// is allowed even if the name has already been poisoned.
Copy link
Contributor

Choose a reason for hiding this comment

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

If the name was already poisoned, is the new entry still poisoned? That's particularly important to capture since we're looking at storing instructions for poisoned names, so "has instruction and is poisoned" is becoming a valid state. Maybe:

Suggested change
// Adds a new name known to not exist. The new entry may not be poisoned. This
// is allowed even if the name has already been poisoned.
// Adds a new name known to not exist. The new entry won't be poisoned, and
// can overwrite poisoned names.

Comment on lines +75 to +77
// Searches for the given name and returns an EntryId if found or nullopt if
// not. If the name is not found, it will be poisoned so it can't be declared
// later.
Copy link
Contributor

@jonmeow jonmeow Jan 8, 2025

Choose a reason for hiding this comment

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

Would it be worth phrasing this closer to how LookupOrAdd's comment is phrased? e.g.:

Suggested change
// Searches for the given name and returns an EntryId if found or nullopt if
// not. If the name is not found, it will be poisoned so it can't be declared
// later.
// If the given name already exists, return the EntryId; the entry might be
// poisoned. Otherwise, poisons the name so that it can't be declared later
// and returns nullopt.

Copy link
Contributor

@jonmeow jonmeow Jan 8, 2025

Choose a reason for hiding this comment

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

(note, either way, I think it'd be good to have "if not found" behavior grouped instead of "or nullopt if not. If the name is not found,")

@zygoloid
Copy link
Contributor Author

zygoloid commented Jan 8, 2025

What I thought we were trying to do before is when lookup was successful, poison the name in all the scopes between where the name was found and where we started looking. From the test change, it seems that now we poison a name even when the lookup is not successful. Is this what we want?

The main behavior change is that we now poison the name in the case where lookup is successful and finds a lexical result. Previously we only poisoned the name when lookup was successful and found a non-lexical result. Ultimately, the idea is that any time we look for a name in a declarative scope, other than when declaring it, it's an error if we don't find it and we later add it. So poisoning the name as part of the lookup seems like the right model.

In the case of a failed lookup followed by a declaration, I do agree that the best thing would be to produce only a single error. Diagnosing both the failed lookup and the declaration of a poisoned name doesn't seem terrible to me -- it provides all the information that we have, which I think is probably a little more useful than diagnosing only the use of the undeclared identifier. Ideally I think I'd want us to produce a single "use of name before its declaration" error for the use that also points at where the name is later declared. That said, we can't really do that unless we produce diagnostics out of order (or modify the diagnostic after we initially emit it), and I'm nervous about building dependencies on diagnostic reordering given #3054, so we might need to think a bit about how to fit that into our diagnostic infrastructure.

@bricknerb
Copy link
Contributor

Can you add some context for why #4622 is mentioned in the PR description?

I've added it as this change seems to be part of name poisoning feature.

@bricknerb
Copy link
Contributor

What I thought we were trying to do before is when lookup was successful, poison the name in all the scopes between where the name was found and where we started looking. From the test change, it seems that now we poison a name even when the lookup is not successful. Is this what we want?

The main behavior change is that we now poison the name in the case where lookup is successful and finds a lexical result. Previously we only poisoned the name when lookup was successful and found a non-lexical result. Ultimately, the idea is that any time we look for a name in a declarative scope, other than when declaring it, it's an error if we don't find it and we later add it. So poisoning the name as part of the lookup seems like the right model.

In the case of a failed lookup followed by a declaration, I do agree that the best thing would be to produce only a single error. Diagnosing both the failed lookup and the declaration of a poisoned name doesn't seem terrible to me -- it provides all the information that we have, which I think is probably a little more useful than diagnosing only the use of the undeclared identifier. Ideally I think I'd want us to produce a single "use of name before its declaration" error for the use that also points at where the name is later declared. That said, we can't really do that unless we produce diagnostics out of order (or modify the diagnostic after we initially emit it), and I'm nervous about building dependencies on diagnostic reordering given #3054, so we might need to think a bit about how to fit that into our diagnostic infrastructure.

Thanks for clarifying!

Regarding the behavior change, I'm not sure I understand what is a lexical result vs. a non-lexical result here.
I don't think we have a test that covers that (the modified test in this PR is for a failed lookup, AFAIU), so adding a test would probably clarify what is changing and makes sure it stays that way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants