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
Open
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
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 @@ -695,20 +695,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 @@ -2392,7 +2393,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 @@ -2429,6 +2431,188 @@ 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 mut lexical_scope_opt = Some(module.current_lexical_scope());
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;
}
}
Comment on lines +2443 to +2460
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?


// 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 @@ -333,8 +333,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
Loading