Skip to content

Commit 56b42cb

Browse files
authored
Dereferencing operator . on tuple element access (#5552)
## Description This PR implements tuple element 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 tuples using this recursive definition: `<reference>.<element index> := (*<reference>).<element index>`. This eliminates the need for the dereferencing operator `*` when working with references to tuples: ```Sway let t = (1, 2, 3); let r = &&&t; assert((***r).0 == r.0); ``` ```Sway let r = &&& ( &&(1, 2), &(3, 4) ); assert(r.0.0 == 1); assert(r.1.1 == 4); ``` Additionally, the PR: - creates `Diagnostic` for the `TupleIndexOutOfBounds` error. - harmonizes the appearance of the `TupleIndexOutOfBounds` error in the element access and reassignments. ## Demo `TupleIndexOutOfBounds` in element access and reassignments before: ![Tuple index out of bounds - Before](https://github.com/FuelLabs/sway/assets/4142833/38dc610e-c9c2-4ae9-a638-d3da3fc38ada) New errors: ![Tuple index out of bounds - After](https://github.com/FuelLabs/sway/assets/4142833/d1668f7d-15b6-4d76-8644-301d3871568d) ![Tuple element access requires a tuple](https://github.com/FuelLabs/sway/assets/4142833/15c41cea-ccac-417c-9b7b-758dccbc43f8) ## 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 d3152bb commit 56b42cb

File tree

16 files changed

+720
-86
lines changed

16 files changed

+720
-86
lines changed

sway-core/src/ir_generation/function.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,7 @@ impl<'eng> FnCompiler<'eng> {
13451345

13461346
let referenced_type = self.engines.te().get_unaliased(referenced_ast_type);
13471347

1348-
let result = if referenced_type.is_copy_type() || referenced_type.is_reference_type() {
1348+
let result = if referenced_type.is_copy_type() || referenced_type.is_reference() {
13491349
// For non aggregates, we need to return the value.
13501350
// This means, loading the value the `ptr` is pointing to.
13511351
self.current_block.append(context).load(ptr)
@@ -3005,7 +3005,7 @@ impl<'eng> FnCompiler<'eng> {
30053005
if initializer_val
30063006
.get_type(context)
30073007
.map_or(false, |ty| ty.is_ptr(context))
3008-
&& (init_type.is_copy_type() || init_type.is_reference_type())
3008+
&& (init_type.is_copy_type() || init_type.is_reference())
30093009
{
30103010
// It's a pointer to a copy type, or a reference behind a pointer. We need to dereference it.
30113011
// We can get a reference behind a pointer if a reference variable is passed to the ASM block.

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

+7
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ pub enum TyExpressionVariant {
103103
TupleElemAccess {
104104
prefix: Box<TyExpression>,
105105
elem_to_access_num: usize,
106+
/// Final resolved type of the `prefix` part
107+
/// of the expression. This will always be
108+
/// a [TypeId] of a tuple, never an alias
109+
/// or a reference to a tuple.
110+
/// The original parent might be an alias
111+
/// or a direct or indirect reference to a
112+
/// tuple.
106113
resolved_type_of_parent: TypeId,
107114
elem_to_access_span: Span,
108115
},

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1855,7 +1855,7 @@ impl ty::TyExpression {
18551855
_ => {
18561856
return Err(handler.emit_err(CompileError::NotIndexable {
18571857
actually: engines.help_out(prefix_type_id).to_string(),
1858-
span: prefix_span.clone(),
1858+
span: prefix_span,
18591859
}))
18601860
}
18611861
};

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub(crate) fn instantiate_struct_field_access(
5252
actually: engines.help_out(prefix_type_id).to_string(),
5353
storage_variable: None,
5454
field_name: (&field_to_access).into(),
55-
span: prefix_span.clone(),
55+
span: prefix_span,
5656
}))
5757
}
5858
};
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use sway_error::handler::{ErrorEmitted, Handler};
22
use sway_types::Span;
33

4-
use crate::{language::ty, CompileError, Engines};
4+
use crate::{language::ty, CompileError, Engines, TypeInfo};
55

66
pub(crate) fn instantiate_tuple_index_access(
77
handler: &Handler,
@@ -12,33 +12,69 @@ pub(crate) fn instantiate_tuple_index_access(
1212
span: Span,
1313
) -> Result<ty::TyExpression, ErrorEmitted> {
1414
let type_engine = engines.te();
15-
let mut tuple_type_arg_to_access = None;
16-
let type_info = type_engine.get(parent.return_type);
17-
let type_args = type_info.expect_tuple(handler, engines, &parent.span)?;
18-
for (pos, type_arg) in type_args.iter().enumerate() {
19-
if pos == index {
20-
tuple_type_arg_to_access = Some(type_arg.clone());
21-
}
15+
16+
let mut current_prefix_te = Box::new(parent);
17+
let mut current_type = type_engine.get_unaliased(current_prefix_te.return_type);
18+
19+
let prefix_type_id = current_prefix_te.return_type;
20+
let prefix_span = current_prefix_te.span.clone();
21+
22+
// Create the prefix part of the final tuple element access expression.
23+
// This might be an expression that directly evaluates to a tuple type,
24+
// or an arbitrary number of dereferencing expressions where the last one
25+
// dereference to a tuple type.
26+
//
27+
// We will either hit a tuple at the end or return an error, so the
28+
// loop cannot be endless.
29+
while !current_type.is_tuple() {
30+
match &*current_type {
31+
TypeInfo::Ref(referenced_type) => {
32+
let referenced_type_id = referenced_type.type_id;
33+
34+
current_prefix_te = Box::new(ty::TyExpression {
35+
expression: ty::TyExpressionVariant::Deref(current_prefix_te),
36+
return_type: referenced_type_id,
37+
span: prefix_span.clone(),
38+
});
39+
40+
current_type = type_engine.get_unaliased(referenced_type_id);
41+
}
42+
TypeInfo::ErrorRecovery(err) => return Err(*err),
43+
_ => {
44+
return Err(
45+
handler.emit_err(CompileError::TupleElementAccessOnNonTuple {
46+
actually: engines.help_out(prefix_type_id).to_string(),
47+
span: prefix_span,
48+
index,
49+
index_span,
50+
}),
51+
)
52+
}
53+
};
2254
}
23-
let tuple_type_arg_to_access = match tuple_type_arg_to_access {
24-
Some(tuple_type_arg_to_access) => tuple_type_arg_to_access,
25-
None => {
26-
return Err(handler.emit_err(CompileError::TupleIndexOutOfBounds {
27-
index,
28-
count: type_args.len(),
29-
span: index_span,
30-
}));
31-
}
55+
56+
let TypeInfo::Tuple(type_args) = &*current_type else {
57+
panic!("The current type must be a tuple.");
3258
};
33-
let exp = ty::TyExpression {
59+
60+
if type_args.len() <= index {
61+
return Err(handler.emit_err(CompileError::TupleIndexOutOfBounds {
62+
index,
63+
count: type_args.len(),
64+
tuple_type: engines.help_out(prefix_type_id).to_string(),
65+
span: index_span,
66+
prefix_span,
67+
}));
68+
}
69+
70+
Ok(ty::TyExpression {
3471
expression: ty::TyExpressionVariant::TupleElemAccess {
35-
resolved_type_of_parent: parent.return_type,
36-
prefix: Box::new(parent),
72+
resolved_type_of_parent: current_prefix_te.return_type,
73+
prefix: current_prefix_te,
3774
elem_to_access_num: index,
3875
elem_to_access_span: index_span,
3976
},
40-
return_type: tuple_type_arg_to_access.type_id,
77+
return_type: type_args[index].type_id,
4178
span,
42-
};
43-
Ok(exp)
79+
})
4480
}

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

+17-6
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,9 @@ impl Items {
427427
return Err(handler.emit_err(CompileError::TupleIndexOutOfBounds {
428428
index: *index,
429429
count: fields.len(),
430-
span: Span::join(full_span_for_error, index_span.clone()),
430+
tuple_type: engines.help_out(symbol).to_string(),
431+
span: index_span.clone(),
432+
prefix_span: full_span_for_error.clone(),
431433
}));
432434
}
433435
};
@@ -460,11 +462,20 @@ impl Items {
460462
span: full_span_for_error,
461463
}));
462464
}
463-
(actually, ty::ProjectionKind::TupleField { .. }) => {
464-
return Err(handler.emit_err(CompileError::NotATuple {
465-
actually: engines.help_out(actually).to_string(),
466-
span: full_span_for_error,
467-
}));
465+
(
466+
actually,
467+
ty::ProjectionKind::TupleField {
468+
index, index_span, ..
469+
},
470+
) => {
471+
return Err(
472+
handler.emit_err(CompileError::TupleElementAccessOnNonTuple {
473+
actually: engines.help_out(actually).to_string(),
474+
span: full_span_for_error,
475+
index: *index,
476+
index_span: index_span.clone(),
477+
}),
478+
);
468479
}
469480
(actually, ty::ProjectionKind::ArrayIndex { .. }) => {
470481
return Err(handler.emit_err(CompileError::NotIndexable {

sway-core/src/type_system/info.rs

+12-48
Original file line numberDiff line numberDiff line change
@@ -1053,13 +1053,6 @@ impl TypeInfo {
10531053
}
10541054
}
10551055

1056-
pub fn is_unit(&self) -> bool {
1057-
match self {
1058-
TypeInfo::Tuple(fields) => fields.is_empty(),
1059-
_ => false,
1060-
}
1061-
}
1062-
10631056
// TODO-IG: Check all the usages of `is_copy_type`.
10641057
pub fn is_copy_type(&self) -> bool {
10651058
// XXX This is FuelVM specific. We need to find the users of this method and determine
@@ -1084,7 +1077,14 @@ impl TypeInfo {
10841077
}
10851078
}
10861079

1087-
pub fn is_reference_type(&self) -> bool {
1080+
pub fn is_unit(&self) -> bool {
1081+
match self {
1082+
TypeInfo::Tuple(fields) => fields.is_empty(),
1083+
_ => false,
1084+
}
1085+
}
1086+
1087+
pub fn is_reference(&self) -> bool {
10881088
matches!(self, TypeInfo::Ref(_))
10891089
}
10901090

@@ -1096,6 +1096,10 @@ impl TypeInfo {
10961096
matches!(self, TypeInfo::Struct(_))
10971097
}
10981098

1099+
pub fn is_tuple(&self) -> bool {
1100+
matches!(self, TypeInfo::Tuple(_))
1101+
}
1102+
10991103
pub(crate) fn apply_type_arguments(
11001104
self,
11011105
handler: &Handler,
@@ -1296,46 +1300,6 @@ impl TypeInfo {
12961300
}
12971301
}
12981302

1299-
/// Given a [TypeInfo] `self`, expect that `self` is a [TypeInfo::Tuple], or a
1300-
/// [TypeInfo::Alias] of a tuple type. Also, return the contents of the tuple.
1301-
///
1302-
/// Note that this works recursively. That is, it supports situations where a tuple has a chain
1303-
/// of aliases such as:
1304-
///
1305-
/// ```
1306-
/// type Alias1 = (u64, u64);
1307-
/// type Alias2 = Alias1;
1308-
///
1309-
/// fn foo(t: Alias2) {
1310-
/// let x = t.0;
1311-
/// }
1312-
/// ```
1313-
///
1314-
/// Returns an error if `self` is not a [TypeInfo::Tuple] or a [TypeInfo::Alias] of a tuple
1315-
/// type, transitively.
1316-
pub(crate) fn expect_tuple(
1317-
&self,
1318-
handler: &Handler,
1319-
engines: &Engines,
1320-
debug_span: &Span,
1321-
) -> Result<Vec<TypeArgument>, ErrorEmitted> {
1322-
match self {
1323-
TypeInfo::Tuple(elems) => Ok(elems.to_vec()),
1324-
TypeInfo::Alias {
1325-
ty: TypeArgument { type_id, .. },
1326-
..
1327-
} => engines
1328-
.te()
1329-
.get(*type_id)
1330-
.expect_tuple(handler, engines, debug_span),
1331-
TypeInfo::ErrorRecovery(err) => Err(*err),
1332-
a => Err(handler.emit_err(CompileError::NotATuple {
1333-
actually: engines.help_out(a).to_string(),
1334-
span: debug_span.clone(),
1335-
})),
1336-
}
1337-
}
1338-
13391303
/// Given a [TypeInfo] `self`, expect that `self` is a [TypeInfo::Enum], or a [TypeInfo::Alias]
13401304
/// of a enum type. Also, return the contents of the enum.
13411305
///

sway-error/src/error.rs

+48-5
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,13 @@ pub enum CompileError {
303303
field_name: IdentUnique,
304304
span: Span,
305305
},
306-
#[error("This is a {actually}, not a tuple. Elements can only be access on tuples.")]
307-
NotATuple { actually: String, span: Span },
306+
#[error("This expression has type \"{actually}\", which is not a tuple. Elements can only be accessed on tuples.")]
307+
TupleElementAccessOnNonTuple {
308+
actually: String,
309+
span: Span,
310+
index: usize,
311+
index_span: Span,
312+
},
308313
#[error("This expression has type \"{actually}\", which is not an indexable type.")]
309314
NotIndexable { actually: String, span: Span },
310315
#[error("\"{name}\" is a {actually}, not an enum.")]
@@ -555,11 +560,13 @@ pub enum CompileError {
555560
InvalidOpcodeFromPredicate { opcode: String, span: Span },
556561
#[error("Array index out of bounds; the length is {count} but the index is {index}.")]
557562
ArrayOutOfBounds { index: u64, count: u64, span: Span },
558-
#[error("Tuple index out of bounds; the arity is {count} but the index is {index}.")]
563+
#[error("Tuple index {index} is out of bounds. The tuple has {count} element{}.", plural_s(*count))]
559564
TupleIndexOutOfBounds {
560565
index: usize,
561566
count: usize,
567+
tuple_type: String,
562568
span: Span,
569+
prefix_span: Span,
563570
},
564571
#[error("Constants cannot be shadowed. {variable_or_constant} \"{name}\" shadows constant with the same name.")]
565572
ConstantsCannotBeShadowed {
@@ -894,7 +901,7 @@ impl Spanned for CompileError {
894901
StructFieldDoesNotExist { field_name, .. } => field_name.span(),
895902
MethodNotFound { span, .. } => span.clone(),
896903
ModuleNotFound { span, .. } => span.clone(),
897-
NotATuple { span, .. } => span.clone(),
904+
TupleElementAccessOnNonTuple { span, .. } => span.clone(),
898905
NotAStruct { span, .. } => span.clone(),
899906
NotIndexable { span, .. } => span.clone(),
900907
FieldAccessOnNonStruct { span, .. } => span.clone(),
@@ -1818,6 +1825,42 @@ impl ToDiagnostic for CompileError {
18181825
},
18191826
help: vec![],
18201827
},
1828+
TupleIndexOutOfBounds { index, count, tuple_type, span, prefix_span } => Diagnostic {
1829+
reason: Some(Reason::new(code(1), "Tuple index is out of bounds".to_string())),
1830+
issue: Issue::error(
1831+
source_engine,
1832+
span.clone(),
1833+
format!("Tuple index {index} is out of bounds. The tuple has only {count} element{}.", plural_s(*count))
1834+
),
1835+
hints: vec![
1836+
Hint::info(
1837+
source_engine,
1838+
prefix_span.clone(),
1839+
format!("This expression has type \"{tuple_type}\".")
1840+
),
1841+
],
1842+
help: vec![],
1843+
},
1844+
TupleElementAccessOnNonTuple { actually, span, index, index_span } => Diagnostic {
1845+
reason: Some(Reason::new(code(1), "Tuple element access requires a tuple".to_string())),
1846+
issue: Issue::error(
1847+
source_engine,
1848+
span.clone(),
1849+
format!("This expression has type \"{actually}\", which is not a tuple or a reference to a tuple.")
1850+
),
1851+
hints: vec![
1852+
Hint::info(
1853+
source_engine,
1854+
index_span.clone(),
1855+
format!("Tuple element access happens here, on the index {index}.")
1856+
)
1857+
],
1858+
help: vec![
1859+
"In Sway, tuple elements can be accessed on:".to_string(),
1860+
format!("{}- tuples. E.g., `my_tuple.1`.", Indent::Single),
1861+
format!("{}- references, direct or indirect, to tuples. E.g., `(&my_tuple).1` or `(&&&my_tuple).1`.", Indent::Single),
1862+
],
1863+
},
18211864
_ => Diagnostic {
18221865
// TODO: Temporary we use self here to achieve backward compatibility.
18231866
// In general, self must not be used and will not be used once we
@@ -1861,7 +1904,7 @@ pub enum StructFieldUsageContext {
18611904
StorageAccess,
18621905
PatternMatching { has_rest_pattern: bool },
18631906
StructFieldAccess,
1864-
// TODO: Distinguish between struct filed access and destructing
1907+
// TODO: Distinguish between struct field access and destructing
18651908
// once https://github.com/FuelLabs/sway/issues/5478 is implemented
18661909
// and provide specific suggestions for these two cases.
18671910
// (Destructing desugars to plain struct field access.)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[[package]]
2+
name = "core"
3+
source = "path+from-root-F898797E7BC715F8"
4+
5+
[[package]]
6+
name = "dereferencing_operator_dot_on_tuples"
7+
source = "member"
8+
dependencies = ["std"]
9+
10+
[[package]]
11+
name = "std"
12+
source = "path+from-root-F898797E7BC715F8"
13+
dependencies = ["core"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[project]
2+
authors = ["Fuel Labs <contact@fuel.sh>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "dereferencing_operator_dot_on_tuples"
6+
7+
[dependencies]
8+
std = { path = "../../../../../../../../sway-lib-std" }

0 commit comments

Comments
 (0)