Skip to content

Commit 575d2bb

Browse files
authored
Dereferencing operator . on struct field access (#5538)
## Description This PR implements struct field access operator `.` for [references](https://github.com/FuelLabs/sway-rfcs/blob/ironcev/amend-references/files/0010-references.sw). The overall effort related to references is tracked in #5063. `.` is defined for references to structs using this recursive definition: `<reference>.<field name> := (*<reference>).<field name>`. This eliminates the need for the dereferencing operator `*` when working with references to structs: ```Sway let s = Struct { x: 0 }; let r = &&&s; assert((***r).x == r.x); ``` ```Sway let r = &&&Struct { r_a: &&A { x: 1 }, &B { y: 2 } ]; assert(r.r_a.x == 1); assert(r.r_b.y == 2); ``` Additionally, the PR adds a `Diagnostic` for the `StorageFieldDoesNotExist` error and aligns it with the `StructFieldDoesNotExist` error. ## Demo ![Field access requires a struct - On expression](https://github.com/FuelLabs/sway/assets/4142833/6dcad917-2a91-47ba-8ff7-aa13dc681bcb)) ![Field access requires a struct - Storage variable](https://github.com/FuelLabs/sway/assets/4142833/ab3fc2d4-bc87-48dd-855c-b4001c43199e) ![Storage field does not exist](https://github.com/FuelLabs/sway/assets/4142833/6a7f1800-dbad-4e0a-af4a-ed5f9f393f70) ## Checklist - [x] I have linked to any relevant issues. - [x] 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). - [x] 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. - [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.
1 parent d6734b4 commit 575d2bb

File tree

25 files changed

+947
-235
lines changed

25 files changed

+947
-235
lines changed

sway-core/src/ir_generation/types.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,25 @@ pub(super) fn get_struct_for_types(
8282
Ok(Type::new_struct(context, types))
8383
}
8484

85+
/// For the [TypeInfo::Struct] given by `struct_type_id` and the
86+
/// [ty::ProjectionKind::StructField] given by `field_kind`
87+
/// returns the name of the struct, and the field index within
88+
/// the struct together with the field [TypeId] if the field exists
89+
/// on the struct.
90+
///
91+
/// Returns `None` if the `struct_type_id` is not a [TypeInfo::Struct]
92+
/// or an alias to a [TypeInfo::Struct] or if the `field_kind`
93+
/// is not a [ty::ProjectionKind::StructField].
8594
pub(super) fn get_struct_name_field_index_and_type(
8695
type_engine: &TypeEngine,
8796
decl_engine: &DeclEngine,
88-
field_type: TypeId,
97+
struct_type_id: TypeId,
8998
field_kind: ty::ProjectionKind,
9099
) -> Option<(String, Option<(u64, TypeId)>)> {
91-
let ty_info = type_engine
92-
.to_typeinfo(field_type, &field_kind.span())
100+
let struct_ty_info = type_engine
101+
.to_typeinfo(struct_type_id, &field_kind.span())
93102
.ok()?;
94-
match (ty_info, &field_kind) {
103+
match (struct_ty_info, &field_kind) {
95104
(TypeInfo::Struct(decl_ref), ty::ProjectionKind::StructField { name: field_name }) => {
96105
let decl = decl_engine.get_struct(&decl_ref);
97106
Some((
@@ -110,7 +119,7 @@ pub(super) fn get_struct_name_field_index_and_type(
110119
},
111120
_,
112121
) => get_struct_name_field_index_and_type(type_engine, decl_engine, type_id, field_kind),
113-
_otherwise => None,
122+
_ => None,
114123
}
115124
}
116125

sway-core/src/language/ty/declaration/storage.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,9 @@ impl TyStorageDecl {
9898
}
9999
None => {
100100
return Err(handler.emit_err(CompileError::StorageFieldDoesNotExist {
101-
name: first_field.clone(),
102-
span: first_field.span(),
101+
field_name: first_field.into(),
102+
available_fields: storage_fields.iter().map(|sf| sf.name.clone()).collect(),
103+
storage_decl_span: self.span(),
103104
}));
104105
}
105106
};
@@ -113,6 +114,13 @@ impl TyStorageDecl {
113114
previous_field = first_field;
114115
previous_field_type_id = initial_field_type;
115116

117+
// Storage cannot contain references, so there is no need for checking
118+
// if the declaration is a reference to a struct. References can still
119+
// be erroneously declared in the storage, and the type behind a concrete
120+
// field access might be a reference to struct, but we do not treat that
121+
// as a special case but just another one "not a struct".
122+
// The FieldAccessOnNonStruct error message will explain that in the case
123+
// of storage access, fields can be accessed only on structs.
116124
let get_struct_decl = |type_id: TypeId| match &*type_engine.get(type_id) {
117125
TypeInfo::Struct(decl_ref) => Some(decl_engine.get_struct(decl_ref)),
118126
_ => None,
@@ -191,8 +199,10 @@ impl TyStorageDecl {
191199
}
192200
None => {
193201
return Err(handler.emit_err(CompileError::FieldAccessOnNonStruct {
194-
span: previous_field.span(),
195202
actually: engines.help_out(previous_field_type_id).to_string(),
203+
storage_variable: Some(previous_field.to_string()),
204+
field_name: field.into(),
205+
span: previous_field.span(),
196206
}))
197207
}
198208
};

sway-core/src/language/ty/expression/expression_variant.rs

+7
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ pub enum TyExpressionVariant {
9191
prefix: Box<TyExpression>,
9292
field_to_access: TyStructField,
9393
field_instantiation_span: Span,
94+
/// Final resolved type of the `prefix` part
95+
/// of the expression. This will always be
96+
/// a [TypeId] of a struct, never an alias
97+
/// or a reference to a struct.
98+
/// The original parent might be an alias
99+
/// or a direct or indirect reference to a
100+
/// struct.
94101
resolved_type_of_parent: TypeId,
95102
},
96103
TupleElemAccess {

sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,7 @@ impl ty::TyExpression {
18511851

18521852
current_type = type_engine.get_unaliased(referenced_type_id);
18531853
}
1854+
TypeInfo::ErrorRecovery(err) => return Err(*err),
18541855
_ => {
18551856
return Err(handler.emit_err(CompileError::NotIndexable {
18561857
actually: engines.help_out(prefix_type_id).to_string(),
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
use sway_error::handler::{ErrorEmitted, Handler};
1+
use sway_error::{
2+
error::{CompileError, StructFieldUsageContext},
3+
handler::{ErrorEmitted, Handler},
4+
warning::{CompileWarning, Warning},
5+
};
26
use sway_types::{Ident, Span, Spanned};
37

4-
use crate::{language::ty, Engines, Namespace};
8+
use crate::{
9+
language::ty::{self, StructAccessInfo},
10+
Engines, Namespace, TypeInfo,
11+
};
512

613
pub(crate) fn instantiate_struct_field_access(
714
handler: &Handler,
@@ -12,23 +19,101 @@ pub(crate) fn instantiate_struct_field_access(
1219
span: Span,
1320
) -> Result<ty::TyExpression, ErrorEmitted> {
1421
let type_engine = engines.te();
15-
let field_instantiation_span = field_to_access.span();
16-
let field = type_engine.get(parent.return_type).apply_subfields(
17-
handler,
18-
engines,
19-
namespace,
20-
&[field_to_access],
21-
&parent.span,
22-
)?;
23-
let exp = ty::TyExpression {
22+
23+
let mut current_prefix_te = Box::new(parent);
24+
let mut current_type = type_engine.get_unaliased(current_prefix_te.return_type);
25+
26+
let prefix_type_id = current_prefix_te.return_type;
27+
let prefix_span = current_prefix_te.span.clone();
28+
29+
// Create the prefix part of the final struct field access expression.
30+
// This might be an expression that directly evaluates to a struct type,
31+
// or an arbitrary number of dereferencing expressions where the last one
32+
// dereferences to a struct type.
33+
//
34+
// We will either hit a struct at the end or return an error, so the
35+
// loop cannot be endless.
36+
while !current_type.is_struct() {
37+
match &*current_type {
38+
TypeInfo::Ref(referenced_type) => {
39+
let referenced_type_id = referenced_type.type_id;
40+
41+
current_prefix_te = Box::new(ty::TyExpression {
42+
expression: ty::TyExpressionVariant::Deref(current_prefix_te),
43+
return_type: referenced_type_id,
44+
span: prefix_span.clone(),
45+
});
46+
47+
current_type = type_engine.get_unaliased(referenced_type_id);
48+
}
49+
TypeInfo::ErrorRecovery(err) => return Err(*err),
50+
_ => {
51+
return Err(handler.emit_err(CompileError::FieldAccessOnNonStruct {
52+
actually: engines.help_out(prefix_type_id).to_string(),
53+
storage_variable: None,
54+
field_name: (&field_to_access).into(),
55+
span: prefix_span.clone(),
56+
}))
57+
}
58+
};
59+
}
60+
61+
let TypeInfo::Struct(struct_decl_ref) = &*current_type else {
62+
panic!("The current type must be a struct.");
63+
};
64+
65+
let decl = engines.de().get_struct(struct_decl_ref);
66+
let (struct_can_be_changed, is_public_struct_access) =
67+
StructAccessInfo::get_info(&decl, namespace).into();
68+
69+
let field = match decl.find_field(&field_to_access) {
70+
Some(field) => {
71+
if is_public_struct_access && field.is_private() {
72+
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
73+
// https://github.com/FuelLabs/sway/issues/5520
74+
// return Err(handler.emit_err(CompileError::StructFieldIsPrivate {
75+
// field_name: (&field_to_access).into(),
76+
// struct_name: decl.call_path.suffix.clone(),
77+
// field_decl_span: field.name.span(),
78+
// struct_can_be_changed,
79+
// usage_context: StructFieldUsageContext::StructFieldAccess,
80+
// }));
81+
handler.emit_warn(CompileWarning {
82+
span: field_to_access.span(),
83+
warning_content: Warning::StructFieldIsPrivate {
84+
field_name: (&field_to_access).into(),
85+
struct_name: decl.call_path.suffix.clone(),
86+
field_decl_span: field.name.span(),
87+
struct_can_be_changed,
88+
usage_context: StructFieldUsageContext::StructFieldAccess,
89+
},
90+
});
91+
}
92+
93+
field.clone()
94+
}
95+
None => {
96+
return Err(handler.emit_err(CompileError::StructFieldDoesNotExist {
97+
field_name: (&field_to_access).into(),
98+
available_fields: decl.accessible_fields_names(is_public_struct_access),
99+
is_public_struct_access,
100+
struct_name: decl.call_path.suffix.clone(),
101+
struct_decl_span: decl.span(),
102+
struct_is_empty: decl.is_empty(),
103+
usage_context: StructFieldUsageContext::StructFieldAccess,
104+
}));
105+
}
106+
};
107+
108+
let return_type = field.type_argument.type_id;
109+
Ok(ty::TyExpression {
24110
expression: ty::TyExpressionVariant::StructFieldAccess {
25-
resolved_type_of_parent: parent.return_type,
26-
prefix: Box::new(parent),
27-
field_to_access: field.clone(),
28-
field_instantiation_span,
111+
resolved_type_of_parent: current_prefix_te.return_type,
112+
prefix: current_prefix_te,
113+
field_to_access: field,
114+
field_instantiation_span: field_to_access.span(),
29115
},
30-
return_type: field.type_argument.type_id,
116+
return_type,
31117
span,
32-
};
33-
Ok(exp)
118+
})
34119
}

sway-core/src/semantic_analysis/namespace/items.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -452,10 +452,12 @@ impl Items {
452452
// TODO: Include the closing square bracket into the error span.
453453
full_span_for_error = Span::join(full_span_for_error, index_span.clone());
454454
}
455-
(actually, ty::ProjectionKind::StructField { .. }) => {
455+
(actually, ty::ProjectionKind::StructField { name }) => {
456456
return Err(handler.emit_err(CompileError::FieldAccessOnNonStruct {
457-
span: full_span_for_error,
458457
actually: engines.help_out(actually).to_string(),
458+
storage_variable: None,
459+
field_name: name.into(),
460+
span: full_span_for_error,
459461
}));
460462
}
461463
(actually, ty::ProjectionKind::TupleField { .. }) => {

sway-core/src/type_system/info.rs

+7-103
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
use crate::{
22
decl_engine::{DeclEngine, DeclRefEnum, DeclRefStruct},
33
engine_threading::*,
4-
language::{
5-
ty::{self, StructAccessInfo},
6-
CallPath, QualifiedCallPath,
7-
},
4+
language::{ty, CallPath, QualifiedCallPath},
85
type_system::priv_prelude::*,
9-
Ident, Namespace,
6+
Ident,
107
};
118
use sway_error::{
12-
error::{CompileError, StructFieldUsageContext},
9+
error::CompileError,
1310
handler::{ErrorEmitted, Handler},
14-
warning::{CompileWarning, Warning},
1511
};
1612
use sway_types::{integer_bits::IntegerBits, span::Span, SourceId, Spanned};
1713

@@ -1096,6 +1092,10 @@ impl TypeInfo {
10961092
matches!(self, TypeInfo::Array(_, _))
10971093
}
10981094

1095+
pub fn is_struct(&self) -> bool {
1096+
matches!(self, TypeInfo::Struct(_))
1097+
}
1098+
10991099
pub(crate) fn apply_type_arguments(
11001100
self,
11011101
handler: &Handler,
@@ -1245,102 +1245,6 @@ impl TypeInfo {
12451245
}
12461246
}
12471247

1248-
/// Given a [TypeInfo] `self` and a list of [Ident]'s `subfields`,
1249-
/// iterate through the elements of `subfields` as `subfield`,
1250-
/// and recursively apply `subfield` to `self`.
1251-
///
1252-
/// Returns a [ty::TyStructField] when all `subfields` could be
1253-
/// applied without error.
1254-
///
1255-
/// Returns an error when subfields could not be applied:
1256-
/// 1) in the case where `self` is not a [TypeInfo::Struct]
1257-
/// 2) in the case where `subfields` is empty
1258-
/// 3) in the case where a `subfield` does not exist on `self`
1259-
/// 4) in the case where a `subfield` is private and only public subfields can be accessed
1260-
pub(crate) fn apply_subfields(
1261-
&self,
1262-
handler: &Handler,
1263-
engines: &Engines,
1264-
namespace: &Namespace,
1265-
subfields: &[Ident],
1266-
span: &Span,
1267-
) -> Result<ty::TyStructField, ErrorEmitted> {
1268-
let type_engine = engines.te();
1269-
let decl_engine = engines.de();
1270-
match (self, subfields.split_first()) {
1271-
(TypeInfo::Struct { .. } | TypeInfo::Alias { .. }, None) => {
1272-
panic!("Trying to apply an empty list of subfields");
1273-
}
1274-
(TypeInfo::Struct(decl_ref), Some((first, rest))) => {
1275-
let decl = decl_engine.get_struct(decl_ref);
1276-
let (struct_can_be_changed, is_public_struct_access) =
1277-
StructAccessInfo::get_info(&decl, namespace).into();
1278-
1279-
let field = match decl.find_field(first) {
1280-
Some(field) => {
1281-
if is_public_struct_access && field.is_private() {
1282-
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
1283-
// https://github.com/FuelLabs/sway/issues/5520
1284-
// return Err(handler.emit_err(CompileError::StructFieldIsPrivate {
1285-
// field_name: first.into(),
1286-
// struct_name: decl.call_path.suffix.clone(),
1287-
// field_decl_span: field.name.span(),
1288-
// struct_can_be_changed,
1289-
// usage_context: StructFieldUsageContext::StructFieldAccess,
1290-
// }));
1291-
handler.emit_warn(CompileWarning {
1292-
span: first.span(),
1293-
warning_content: Warning::StructFieldIsPrivate {
1294-
field_name: first.into(),
1295-
struct_name: decl.call_path.suffix.clone(),
1296-
field_decl_span: field.name.span(),
1297-
struct_can_be_changed,
1298-
usage_context: StructFieldUsageContext::StructFieldAccess,
1299-
},
1300-
});
1301-
}
1302-
1303-
field.clone()
1304-
}
1305-
None => {
1306-
return Err(handler.emit_err(CompileError::StructFieldDoesNotExist {
1307-
field_name: first.into(),
1308-
available_fields: decl.accessible_fields_names(is_public_struct_access),
1309-
is_public_struct_access,
1310-
struct_name: decl.call_path.suffix.clone(),
1311-
struct_decl_span: decl.span(),
1312-
struct_is_empty: decl.is_empty(),
1313-
usage_context: StructFieldUsageContext::StructFieldAccess,
1314-
}));
1315-
}
1316-
};
1317-
let field = if rest.is_empty() {
1318-
field
1319-
} else {
1320-
type_engine
1321-
.get(field.type_argument.type_id)
1322-
.apply_subfields(handler, engines, namespace, rest, span)?
1323-
};
1324-
Ok(field)
1325-
}
1326-
(
1327-
TypeInfo::Alias {
1328-
ty: TypeArgument { type_id, .. },
1329-
..
1330-
},
1331-
_,
1332-
) => type_engine
1333-
.get(*type_id)
1334-
.apply_subfields(handler, engines, namespace, subfields, span),
1335-
(TypeInfo::ErrorRecovery(err), _) => Err(*err),
1336-
// TODO-IG: Take a close look on this when implementing dereferencing.
1337-
(type_info, _) => Err(handler.emit_err(CompileError::FieldAccessOnNonStruct {
1338-
actually: format!("{:?}", engines.help_out(type_info)),
1339-
span: span.clone(),
1340-
})),
1341-
}
1342-
}
1343-
13441248
pub(crate) fn can_change(&self, decl_engine: &DeclEngine) -> bool {
13451249
// TODO: there might be an optimization here that if the type params hold
13461250
// only non-dynamic types, then it doesn't matter that there are type params

0 commit comments

Comments
 (0)