Skip to content

Commit

Permalink
Implement hierarchical trait map and namespace lookups. (#6516)
Browse files Browse the repository at this point in the history
## 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.](4043fb5)

[Implement hierarchical namespace
lookups.](486df45)

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

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
  • Loading branch information
tritao authored Dec 2, 2024
1 parent 07dfa55 commit 5423c56
Show file tree
Hide file tree
Showing 12 changed files with 394 additions and 283 deletions.
29 changes: 14 additions & 15 deletions sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
},
*,
},
namespace::{IsExtendingExistingImpl, IsImplSelf, TryInsertingTraitImplOnFailure},
namespace::{IsExtendingExistingImpl, IsImplSelf, TraitMap, TryInsertingTraitImplOnFailure},
semantic_analysis::{
symbol_collection_context::SymbolCollectionContext, AbiMode, ConstShadowingMode,
TyNodeDepGraphNodeId, TypeCheckAnalysis, TypeCheckAnalysisContext, TypeCheckContext,
Expand Down Expand Up @@ -691,20 +691,19 @@ fn type_check_trait_implementation(
ctx.namespace_mut()
.module_mut(engines)
.write(engines, |m| {
m.current_items_mut()
.implemented_traits
.check_if_trait_constraints_are_satisfied_for_type(
handler,
implementing_for,
&trait_supertraits
.iter()
.map(|x| x.into())
.collect::<Vec<_>>(),
block_span,
engines,
TryInsertingTraitImplOnFailure::Yes,
code_block_first_pass.into(),
)
TraitMap::check_if_trait_constraints_are_satisfied_for_type(
handler,
m,
implementing_for,
&trait_supertraits
.iter()
.map(|x| x.into())
.collect::<Vec<_>>(),
block_span,
engines,
TryInsertingTraitImplOnFailure::Yes,
code_block_first_pass.into(),
)
})?;

for (type_arg, type_param) in trait_type_arguments.iter().zip(trait_type_parameters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use crate::{
language::{
parsed::*,
ty::{
self, GetDeclIdent, TyCodeBlock, TyDecl, TyExpression, TyExpressionVariant, TyImplItem,
TyReassignmentTarget, VariableMutability,
self, GetDeclIdent, StructAccessInfo, TyCodeBlock, TyDecl, TyExpression,
TyExpressionVariant, TyImplItem, TyReassignmentTarget, VariableMutability,
},
*,
},
Expand All @@ -38,12 +38,13 @@ use crate::{
use ast_node::declaration::{insert_supertraits_into_namespace, SupertraitOf};
use either::Either;
use indexmap::IndexMap;
use namespace::{LexicalScope, Module, ResolvedDeclaration};
use rustc_hash::FxHashSet;
use std::collections::{HashMap, VecDeque};
use sway_ast::intrinsics::Intrinsic;
use sway_error::{
convert_parse_tree_error::ConvertParseTreeError,
error::CompileError,
error::{CompileError, StructFieldUsageContext},
handler::{ErrorEmitted, Handler},
warning::{CompileWarning, Warning},
};
Expand Down Expand Up @@ -2369,7 +2370,8 @@ impl ty::TyExpression {
let indices = indices.into_iter().rev().collect::<Vec<_>>();
let (ty_of_field, _ty_of_parent) =
ctx.namespace().program_id(engines).read(engines, |m| {
m.current_items().find_subfield_type(
Self::find_subfield_type(
m,
handler,
ctx.engines(),
ctx.namespace(),
Expand Down Expand Up @@ -2406,6 +2408,183 @@ impl ty::TyExpression {
})
}

pub fn find_subfield_type(
module: &Module,
handler: &Handler,
engines: &Engines,
namespace: &Namespace,
base_name: &Ident,
projections: &[ty::ProjectionKind],
) -> Result<(TypeId, TypeId), ErrorEmitted> {
let ret = module.walk_scope_chain(|lexical_scope| {
Self::find_subfield_type_helper(
lexical_scope,
handler,
engines,
namespace,
base_name,
projections,
)
})?;

if let Some(ret) = ret {
Ok(ret)
} else {
// Symbol not found
Err(handler.emit_err(CompileError::UnknownVariable {
var_name: base_name.clone(),
span: base_name.span(),
}))
}
}

/// Returns a tuple where the first element is the [TypeId] of the actual expression, and
/// the second is the [TypeId] of its parent.
fn find_subfield_type_helper(
lexical_scope: &LexicalScope,
handler: &Handler,
engines: &Engines,
namespace: &Namespace,
base_name: &Ident,
projections: &[ty::ProjectionKind],
) -> Result<Option<(TypeId, TypeId)>, ErrorEmitted> {
let type_engine = engines.te();
let decl_engine = engines.de();

let symbol = match lexical_scope.items.symbols.get(base_name).cloned() {
Some(s) => s,
None => {
return Ok(None);
}
};
let mut symbol = match symbol {
ResolvedDeclaration::Parsed(_) => unreachable!(),
ResolvedDeclaration::Typed(ty_decl) => ty_decl.return_type(handler, engines)?,
};
let mut symbol_span = base_name.span();
let mut parent_rover = symbol;
let mut full_span_for_error = base_name.span();
for projection in projections {
let resolved_type = match type_engine.to_typeinfo(symbol, &symbol_span) {
Ok(resolved_type) => resolved_type,
Err(error) => {
return Err(handler.emit_err(CompileError::TypeError(error)));
}
};
match (resolved_type, projection) {
(
TypeInfo::Struct(decl_ref),
ty::ProjectionKind::StructField { name: field_name },
) => {
let struct_decl = decl_engine.get_struct(&decl_ref);
let (struct_can_be_changed, is_public_struct_access) =
StructAccessInfo::get_info(engines, &struct_decl, namespace).into();

let field_type_id = match struct_decl.find_field(field_name) {
Some(struct_field) => {
if is_public_struct_access && struct_field.is_private() {
return Err(handler.emit_err(CompileError::StructFieldIsPrivate {
field_name: field_name.into(),
struct_name: struct_decl.call_path.suffix.clone(),
field_decl_span: struct_field.name.span(),
struct_can_be_changed,
usage_context: StructFieldUsageContext::StructFieldAccess,
}));
}

struct_field.type_argument.type_id
}
None => {
return Err(handler.emit_err(CompileError::StructFieldDoesNotExist {
field_name: field_name.into(),
available_fields: struct_decl
.accessible_fields_names(is_public_struct_access),
is_public_struct_access,
struct_name: struct_decl.call_path.suffix.clone(),
struct_decl_span: struct_decl.span(),
struct_is_empty: struct_decl.is_empty(),
usage_context: StructFieldUsageContext::StructFieldAccess,
}));
}
};
parent_rover = symbol;
symbol = field_type_id;
symbol_span = field_name.span().clone();
full_span_for_error = Span::join(full_span_for_error, &field_name.span());
}
(TypeInfo::Tuple(fields), ty::ProjectionKind::TupleField { index, index_span }) => {
let field_type_opt = {
fields
.get(*index)
.map(|TypeArgument { type_id, .. }| type_id)
};
let field_type = match field_type_opt {
Some(field_type) => field_type,
None => {
return Err(handler.emit_err(CompileError::TupleIndexOutOfBounds {
index: *index,
count: fields.len(),
tuple_type: engines.help_out(symbol).to_string(),
span: index_span.clone(),
prefix_span: full_span_for_error.clone(),
}));
}
};
parent_rover = symbol;
symbol = *field_type;
symbol_span = index_span.clone();
full_span_for_error = Span::join(full_span_for_error, index_span);
}
(
TypeInfo::Array(elem_ty, _),
ty::ProjectionKind::ArrayIndex { index_span, .. },
) => {
parent_rover = symbol;
symbol = elem_ty.type_id;
symbol_span = index_span.clone();
// `index_span` does not contain the enclosing square brackets.
// Which means, if this array index access is the last one before the
// erroneous expression, the `full_span_for_error` will be missing the
// closing `]`. We can live with this small glitch so far. To fix it,
// we would need to bring the full span of the index all the way from
// the parsing stage. An effort that doesn't pay off at the moment.
// TODO: Include the closing square bracket into the error span.
full_span_for_error = Span::join(full_span_for_error, index_span);
}
(actually, ty::ProjectionKind::StructField { name }) => {
return Err(handler.emit_err(CompileError::FieldAccessOnNonStruct {
actually: engines.help_out(actually).to_string(),
storage_variable: None,
field_name: name.into(),
span: full_span_for_error,
}));
}
(
actually,
ty::ProjectionKind::TupleField {
index, index_span, ..
},
) => {
return Err(
handler.emit_err(CompileError::TupleElementAccessOnNonTuple {
actually: engines.help_out(actually).to_string(),
span: full_span_for_error,
index: *index,
index_span: index_span.clone(),
}),
);
}
(actually, ty::ProjectionKind::ArrayIndex { .. }) => {
return Err(handler.emit_err(CompileError::NotIndexable {
actually: engines.help_out(actually).to_string(),
span: full_span_for_error,
}));
}
}
}
Ok(Some((symbol, parent_rover)))
}

fn type_check_ref(
handler: &Handler,
mut ctx: TypeCheckContext<'_>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,7 @@ fn collect_struct_constructors(
// but that would be a way too much of suggestions, and moreover, it is also not a design pattern/guideline
// that we wish to encourage.
namespace.program_id(engines).read(engines, |m| {
m.current_items()
.get_items_for_type(engines, struct_type_id)
m.get_items_for_type(engines, struct_type_id)
.iter()
.filter_map(|item| match item {
ResolvedTraitImplItem::Parsed(_) => unreachable!(),
Expand Down
Loading

0 comments on commit 5423c56

Please sign in to comment.