Skip to content

Commit

Permalink
Implement hierarchical namespace lookups.
Browse files Browse the repository at this point in the history
  • Loading branch information
tritao committed Dec 2, 2024
1 parent 4f2df5e commit d07f1af
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 226 deletions.
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,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;
}
}

// 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 d07f1af

Please sign in to comment.