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

Implement hierarchical trait map and namespace lookups. #6516

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

tritao
Copy link
Contributor

@tritao tritao commented Sep 9, 2024

Description

This PR implements a hierarchical-based trait map and namespace lookup system while type checking.

Previously we added the concept of lexical concepts, which store all the names in a tree-based hierarchy.
But currently we still rely on a single "global" namespace, which is maintained by the type check context as we go down the tree. Thus, so far, all trait and namespace lookup have relied on looking up in that single namespace.

The main idea here is to be able to just rely on the names existing in the lexical scopes, and walk up those lexical scope chains as we need to lookup any name or trait.

The logic is split into these two commits:

Implement hierarchical trait map lookups.

Implement hierarchical namespace lookups.

This PR still does not remove that cloning, which will be done in a separate future PR.

Checklist

  • I have linked to any relevant issues.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have updated the documentation where relevant (API docs, the reference, and the Sway book).
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added (or requested a maintainer to add) the necessary Breaking* or New Feature labels where relevant.
  • I have done my best to ensure that my PR adheres to the Fuel Labs Code Review Standards.
  • I have requested a review from the relevant team or maintainers.

@tritao tritao self-assigned this Sep 23, 2024
@tritao tritao force-pushed the hierarchical-lookups-typechecker branch from dc00260 to 60db3a9 Compare November 18, 2024 01:26
@tritao tritao force-pushed the hierarchical-lookups-typechecker branch from 60db3a9 to 486df45 Compare November 18, 2024 01:28
@tritao tritao changed the title Implement hierarchical namespace lookups. Implement hierarchical trait map and namespace lookups. Nov 18, 2024
@tritao tritao added the compiler: frontend Everything to do with type checking, control flow analysis, and everything between parsing and IRgen label Nov 18, 2024
Copy link

codspeed-hq bot commented Nov 18, 2024

CodSpeed Performance Report

Merging #6516 will not alter performance

Comparing tritao:hierarchical-lookups-typechecker (3029139) with master (519f208)

Summary

✅ 22 untouched benchmarks

@tritao tritao marked this pull request as ready for review November 18, 2024 01:44
@tritao tritao requested review from a team as code owners November 18, 2024 01:44
self.get_items_for_type(engines, type_id)
.into_iter()
.filter_map(|item| match item {
ResolvedTraitImplItem::Parsed(_) => todo!(),
Copy link
Member

Choose a reason for hiding this comment

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

nit: I see that this was just moved from another file, but I think we should replace the todo! with something else, like None

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nit: I see that this was just moved from another file, but I think we should replace the todo! with something else, like None

I don't think it should return None in this case because that would be a silent failure to the dev. I think maybe an unreachable here probably makes the most sense, and this code is not really designed to be called for parsed declarations.

Copy link
Contributor

@jjcnn jjcnn left a comment

Choose a reason for hiding this comment

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

Overall it looks good, but I think there's a common pattern that can be abstracted out, and there's a nagging feeling at the back of my mind that you've introduced a shadowing issue. Let me know what you think - I might just be overthinking things.

Comment on lines +1394 to +1401
let mut all_impld_traits: BTreeSet<(Ident, TypeId)> = Default::default();
while let Some(lexical_scope) = lexical_scope_opt {
all_impld_traits.extend(
lexical_scope
.items
.implemented_traits
.get_implemented_traits(type_id, engines),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't there a shadowing issue here? Something like if a type implements two different traits with the same name but in different scopes, and then we refer to the type using the trait name.

My Rust skills aren't quite good enough to come up with a counter-example, but if there is a shadowing issue then it won't show itself until you eliminate the cloning of trait maps anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Isn't there a shadowing issue here? Something like if a type implements two different traits with the same name but in different scopes, and then we refer to the type using the trait name.

My Rust skills aren't quite good enough to come up with a counter-example, but if there is a shadowing issue then it won't show itself until you eliminate the cloning of trait maps anyway.

I think there should be no shadowing issue since the return is a list of traits, I asked @esdrubal for confirmation and he thinks at most we can get an ambuiguity error due to more traits being returned, which is what should happen I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose my question really is: Is there a case where this algorithm will throw an ambiguity error, but where shadowing actually resolves the ambiguity? I'm not sure, because I'm not sure a trait implementation can shadow another trait implementation.

Copy link
Contributor

Choose a reason for hiding this comment

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

TraitMap::insert is the method responsible for checking that we don't shadow other trait implementations.
TraitMap::insert can insert more methods for impl selfs and should throw an error when methods with the same name are added. However, TraitMap::insert and other methods were not included in this PR. I am waiting for this to get merged so I can start working on those.

Comment on lines +2443 to +2460
while let Some(lexical_scope) = lexical_scope_opt {
let result = Self::find_subfield_type_helper(
lexical_scope,
handler,
engines,
namespace,
base_name,
projections,
)?;
if let Some(result) = result {
return Ok(result);
}
if let Some(parent_scope_id) = lexical_scope.parent {
lexical_scope_opt = module.get_lexical_scope(parent_scope_id);
} else {
lexical_scope_opt = None;
}
}
Copy link
Contributor

@jjcnn jjcnn Nov 26, 2024

Choose a reason for hiding this comment

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

The pattern

let mut lexical_scope_opt = ...;
while let Some(lexical_scope) = lexical_scope_opt {
  ...
  if let Some(parent_scope_id) = lexical_scope.parent {
    lexical_scope_opt = module.get_lexical_scope(parent_scope_id);
  } else {
    lexical_scope_opt = None;
  }
}

is repeated in a couple of places, and seems to capture a structural aspect of hierarchical lexical scopes. Is there a way to abstract this pattern out (ideally so that it is located in the namespace module somewhere), so that traversing the scopes is kept separate from what we are searching for?

Comment on lines +307 to +321
let mut lexical_scope_opt = Some(self.current_lexical_scope());
let mut vec = vec![];
while let Some(lexical_scope) = lexical_scope_opt {
vec.extend(
lexical_scope
.items
.implemented_traits
.get_items_for_type(engines, type_id),
);
if let Some(parent_scope_id) = lexical_scope.parent {
lexical_scope_opt = self.get_lexical_scope(parent_scope_id);
} else {
lexical_scope_opt = None;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The pattern occurs again here.

Comment on lines +331 to +344
let mut lexical_scope_opt = Some(self.current_lexical_scope());
while let Some(lexical_scope) = lexical_scope_opt {
let result = lexical_scope
.items
.resolve_symbol(handler, engines, symbol)?;
if let Some(result) = result {
return Ok(result);
}
if let Some(parent_scope_id) = lexical_scope.parent {
lexical_scope_opt = self.get_lexical_scope(parent_scope_id);
} else {
lexical_scope_opt = None;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Same pattern again.

Comment on lines +1393 to +1406
let mut lexical_scope_opt = Some(module.current_lexical_scope());
let mut all_impld_traits: BTreeSet<(Ident, TypeId)> = Default::default();
while let Some(lexical_scope) = lexical_scope_opt {
all_impld_traits.extend(
lexical_scope
.items
.implemented_traits
.get_implemented_traits(type_id, engines),
);
if let Some(parent_scope_id) = lexical_scope.parent {
lexical_scope_opt = module.get_lexical_scope(parent_scope_id);
} else {
lexical_scope_opt = None;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Same pattern again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler: frontend Everything to do with type checking, control flow analysis, and everything between parsing and IRgen
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants