Struct field privacy (#5508)

## Description

This PR makes struct fields private by default and introduces explicit
public struct fields:

```
pub struct Struct {
    pub public_field: u8,
    private_field: u8,    
}
```

Private fields can be accessed only withing the module in which their
struct is declared.

Error messages are properly orchestrated so that no conflicting or
duplicated messages are emitted. Since the change is a breaking change,
relevant suggestion on how to fix the particular issue are given.

To avoid an abrupt breaking change, the errors are temporarily turned
into warnings. These warnings will become errors in the upcoming
versions of Sway. The demo section below demonstrate how the errors will
look like, and how a one warning equivalent looks now.

Standard library structs like `Vec` or `String`, are adapted where
needed by adding the `pub` keyword to the fields that are accessed
outside of the struct declaration module. Some of these fields should
very likely remain public, but some, like e.g., `RawBytes::ptr` or
`Vec::buf` should be private. Adjusting the standard library to properly
utilize private fields is out of scope of this PR and will be done
separately. I expect breaking changes in the STD once we start modeling
structs keeping encapsulation in mind.

In addition, the PR:
- migrates
[annotate_snippets](https://github.com/rust-lang/annotate-snippets-rs)
crate to its latest version (breaking changes)
- removes a redundant non-existing field error and orchestrates
non-existing field error with privacy errors
- replaces an invalid and misleading error when accessing fields on
storage elements that are not structs

Closes #4692.

## Known limitations

There is an issue in displaying multi-line code snippets, which is
independent of these changes, but wasn't apparent before. The issue is
visible in the demo section below, where the struct bodies are sometimes
not fully shown, and they should be. This issue is reported in #5499 and
will be solved in a separate PR.

## Demo (an excerpt 😄)

### Private struct field is inaccessible

![Private struct field is
inaccessible](8ac07c2b-8135-470b-ad7a-820a4934f232)

![Private struct field is
inaccessible](ca944a7a-e6c4-4b6f-97f1-18000e649452)

### Struct cannot be instantiated

![Struct cannot be instantiated due to inaccessible private
fields](05993416-91d6-4f58-8fd6-8c35c23595f8)

![Struct cannot be instantiated due to inaccessible private
fields](655c17df-a520-45a0-8af4-f1e424ddf085)

### Struct pattern must ignore inaccessible private fields

![Struct pattern must ignore inaccessible private
fields](90396d14-de63-4b08-9f22-e260f406542d)

### Struct pattern has missing fields

![Struct pattern has missing
fields](0ddf44e8-7598-461a-b85b-48006670b0ca)

### Errors temporarily turned into warnings

![Error turned into
warning](ba235248-740f-4fd2-b1fa-29fc35ee8c84)

## 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] 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.
This commit is contained in:
Igor Rončević 2024-01-30 14:15:24 +01:00 committed by GitHub
parent c4e3f13f06
commit 8320c1ba12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
174 changed files with 4115 additions and 464 deletions

15
Cargo.lock generated
View file

@ -104,12 +104,12 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "annotate-snippets"
version = "0.9.2"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e"
checksum = "0a433302f833baa830c0092100c481c7ea768c5981a3c36f549517a502f246dd"
dependencies = [
"anstyle",
"unicode-width",
"yansi-term",
]
[[package]]
@ -8168,15 +8168,6 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "yansi-term"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
dependencies = [
"winapi",
]
[[package]]
name = "zerocopy"
version = "0.7.32"

View file

@ -37,10 +37,10 @@ Note that all branches of the `if` expression must return a value of the same ty
<!-- This section should explain `match` expressions in Sway -->
<!-- match:example:start -->
Sway supports advanced pattern matching through exhaustive `match` expressions. Unlike an `if` statement, a `match` expression asserts **at compile** time that all possible patterns have been matched. If you don't handle all the patterns, you will get compiler error indicating that your `match` expression is non-exhaustive.
Sway supports advanced pattern matching through exhaustive `match` expressions. Unlike an `if` expression, a `match` expression asserts **at compile time** that all possible patterns have been matched. If you don't handle all the patterns, you will get compiler error indicating that your `match` expression is non-exhaustive.
<!-- match:example:end -->
The basic syntax of a `match` statement is as follows:
The basic syntax of a `match` expression is as follows:
```sway
let result = match expression {
@ -52,10 +52,10 @@ let result = match expression {
}
```
Some examples of how you can use a match statement:
Some examples of how you can use a `match` expression:
```sway
{{#include ../../../../examples/match_statements/src/main.sw}}
{{#include ../../../../examples/match_expressions/src/main.sw}}
```
## Loops

View file

@ -2,10 +2,20 @@
<!-- This section should explain methods & associated functions in Sway -->
<!-- methods_af:example:start -->
Methods are similar to functions in that we declare them with the `fn` keyword and they have parameters and return a value. However, unlike functions, _Methods_ are defined within the context of a struct (or enum), and either refers to that type or mutates it. The first parameter of a method is always `self`, which represents the instance of the struct the method is being called on.
## Methods
Methods are similar to [functions](functions.md) in that we declare them with the `fn` keyword and they have parameters and return a value. However, unlike functions, _Methods_ are defined within the context of a struct (or enum), and either refers to that type or mutates it. The first parameter of a method is always `self`, which represents the instance of the struct (or enum) the method is being called on.
## Associated Functions
_Associated functions_ are very similar to _methods_, in that they are also defined in the context of a struct or enum, but they do not actually use any of the data in the struct and as a result do not take _self_ as a parameter. Associated functions could be standalone functions, but they are included in a specific type for organizational or semantic reasons.
### Constructors
Constructors are associated functions that construct, or in other words instantiate, new instances of a type. Their return type is always the type itself. E.g., public structs that have private fields must provide a public constructor, or otherwise they cannot be instantiated outside of the module in which they are declared.
## Declaring Methods and Associated Functions
To declare methods and associated functions for a struct or enum, use an `impl` block. Here, `impl` is short for implementation.
<!-- methods_af:example:end -->

View file

@ -5,9 +5,20 @@
<!-- This section should explain structs in Sway -->
<!-- structs:example:start -->
Structs in Sway are a named grouping of types. You may also be familiar with structs via another name: _product types_. Sway does not make any significantly unique usages of structs; they are similar to most other languages which have structs. If you're coming from an object-oriented background, a struct is like the data attributes of an object.
Those data attributes are called _fields_ and can be either public or private.
Private struct fields can be accessed only within the module in which their struct is declared. Public fields are accessible everywhere where the struct is accessible. This access control on the field level allows more fine grained encapsulation of data.
<!-- structs:example:end -->
Firstly, we declare a struct named `Foo` with two fields. The first field is named `bar` and it accepts values of type `u64`, the second field is named `baz` and it accepts `bool` values.
To explain these concepts, let's take a look at the following example, in which we have a module called _data_structures_.
In that module, we declare a struct named `Foo` with two fields. The first field is named `bar`, it is public and it accepts values of type `u64`. The second field is named `baz`, it is also public and it accepts `bool` values.
In a similar way, we define the structs `Point`, `Line`, and `TupleInStruct`. Since all those structs are public, and all their fields are public, they can be instantiated in other modules using the _struct instantiation syntax_ as shown below.
On the other hand, the struct `StructWithPrivateFields` can be instantiated only within the _data_structures_ module, because it contains private fields. To be able to create instances of such structs outside of the module in which they are declared, the struct must offer [constructor associated functions](methods_and_associated_functions.md#constructors).
```sway
{{#include ../../../../examples/structs/src/data_structures.sw}}

View file

@ -11,12 +11,14 @@ Some basic use cases of storage include declaring an owner address for a contrac
## Storage Accesses Via the `storage` Keyword
Declaring variables in storage requires a `storage` declaration that contains a list of all your variables, their types, and their initial values as follows:
Declaring variables in storage requires a `storage` declaration that contains a list of all your variables, their types, and their initial values. The initial value can be any expression that can be evaluated to a constant during compilation, as follows:
```sway
{{#include ../../../../examples/storage_variables/src/main.sw:storage_declaration}}
```
Imported structs with private fields can be initialized in a `storage` declaration only if they provide a public [constructor](../basics/methods_and_associated_functions.md#constructors) that can be evaluated to a constant during compilation. Otherwise, to store such structs in the contract storage, the [manual storage management](#manual-storage-management) must be used.
To write into a storage variable, you need to use the `storage` keyword as follows:
```sway

View file

@ -11,7 +11,7 @@ If you wish contribute to this reference:
cargo install mdbook
```
3. To [build](https://rust-lang.github.io/mdBook/cli/build.html) the book, ensure you are in `/sway/sway-book` and run:
3. To [build](https://rust-lang.github.io/mdBook/cli/build.html) the book, ensure you are in `/docs/reference` and run:
```bash
mdbook build

View file

@ -2,7 +2,7 @@ library;
// ANCHOR: definition
struct Foo {
bar: u64,
pub bar: u64,
baz: bool,
}
// ANCHOR_END: definition
@ -31,6 +31,18 @@ fn variable_instantiation() {
// Access and write to "baz"
foo.baz = true;
}
fn shorthand_instantiation() {
// Declare variables with the same names as the fields in `Foo`
let bar = 42;
let baz = false;
// Instantiate `foo` as `Foo`
let mut foo = Foo { bar, baz };
// Access and write to "baz"
foo.baz = true;
}
// ANCHOR_END: instantiation
// ANCHOR: destructuring
fn destructuring() {

View file

@ -50,6 +50,8 @@ fn method_usage() {
// ANCHOR: associated_impl
impl Foo {
// this is an associated function because it does not take `self` as a parameter
// it is also a constructor because it instantiates
// and returns a new instance of `Foo`
fn new(number: u64) -> Self {
Self { bar: number }
}

View file

@ -1,8 +1,9 @@
contract;
// ANCHOR: initialization
storage {}
storage {
// variable_name1: variable_type1 = default_value1,
// variable_name2: variable_type2 = default_value2,
// ...
}
// ANCHOR_END: initialization

View file

@ -7,6 +7,7 @@ struct Owner {
}
impl Owner {
// a constructor that can be evaluated to a constant `Owner` during compilation
fn default() -> Self {
Self {
maximum_owners: 10,

View file

@ -4,20 +4,24 @@ A struct in Sway is a `product type` which is a data structure that allows group
## Declaration
The following syntax demonstrates the definition of a struct named `Foo` containing two fields - `bar`, a `u64`, and `baz`, a `bool`.
The following syntax demonstrates the declaration of a struct named `Foo` containing two fields - public field `bar`, a `u64`, and a private field `baz`, a `bool`.
```sway
{{#include ../../../code/language/built-ins/structs/src/lib.sw:definition}}
```
Public fields are accessible in all the modules in which the struct is accessible. Private fields are accessible only within the module in which the struct is declared.
## Instantiation
To instatiate a struct the name of the struct must be used followed by `{}` where the fields from the [declaration](#declaration) must be specified inside the brackets.
To instantiate a struct the name of the struct must be used followed by `{}` where the fields from the [declaration](#declaration) must be specified inside the brackets. Instantiation requires all fields to be initialized, both private and public.
```sway
{{#include ../../../code/language/built-ins/structs/src/lib.sw:instantiation}}
```
Structs with private fields can be instantiated only within the module in which the struct is declared.
## Destructuring
The fields of a struct can be accessed through destructuring.
@ -25,3 +29,5 @@ The fields of a struct can be accessed through destructuring.
```sway
{{#include ../../../code/language/built-ins/structs/src/lib.sw:destructuring}}
```
When destructuring structs with private fields outside of a module in which the struct is defined, the private fields must be omitted by using the `..`.

View file

@ -1,7 +1,9 @@
# Structs
We can match on specific arguments inside a struct while ignoring the rest.
We can match on specific arguments inside a struct while ignoring the rest by using `..`.
```sway
{{#include ../../../../../code/language/control_flow/src/lib.sw:complex_struct_unpacking_match}}
```
If the struct is imported from another module and has private fields, the private fields must always be ignored by using `..`.

View file

@ -4,6 +4,12 @@ Associated functions are similar to methods in that they are also defined in the
Associated functions could be standalone functions, but they are included in a specific type for organizational or semantic reasons.
## Constructors
A distinguished family of associated functions of a specific type are _type constructors_. Constructors are associated functions that construct, or in other words instantiate, new instances of a type. Their return type always includes the type itself, and is often just the type itself.
Public [structs](../built-ins/structs.md) that have private fields must provide a public constructor, or otherwise cannot be instantiated outside of the module in which they are declared.
## Declaration
In this example we will take a look at a struct; however, an enum will work in the same way.

View file

@ -15,7 +15,7 @@ In the following example we will take a look at two ways of storing a struct.
- Explicitly declaring the values in the `storage` block
- Encapsulating the values in an [associated function](../../language/functions/index.md)
We'll begin by defining the `Owner` & `Role` data structures and implement a `default` associated function on the `Owner`.
We'll begin by defining the `Owner` & `Role` data structures and implement a `default` [constructor](../../language/functions/associated-function.md#constructors) on the `Owner`.
```sway
{{#include ../../../code/operations/storage/storage_init/src/main.sw:data_structures}}
@ -27,4 +27,6 @@ Now that we have our data structures we'll keep track of how many `current_owner
{{#include ../../../code/operations/storage/storage_init/src/main.sw:initialization}}
```
An explicit declaration is likely to be sufficient for most types however it may be preferable to encapsulate that functionality for complex types in order to keep the code concise.
An explicit declaration is likely to be sufficient for most types. However, it may be preferable to encapsulate the initialization of complex types within a [constructor](../../language/functions/associated-function.md#constructors) in order to keep the code concise.
Note that the constructors used in `storage` blocks must evaluate to a constant during compilation.

View file

@ -60,7 +60,7 @@ source = "member"
dependencies = ["std"]
[[package]]
name = "match_statements"
name = "match_expressions"
source = "member"
dependencies = ["std"]

View file

@ -15,7 +15,7 @@ members = [
"hashing",
"identity",
"liquidity_pool",
"match_statements",
"match_expressions",
"msg_sender",
"native_asset",
"option",

View file

@ -2,7 +2,7 @@
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "match_statements"
name = "match_expressions"
[dependencies]
std = { path = "../../sway-lib-std" }

View file

@ -12,6 +12,8 @@ impl Foo {
}
// this is an _associated function_, since it does not take `self` as a parameter.
// it is at the same time a _constructor_ because it instantiates and returns
// a new instance of `Foo`.
fn new_foo(number: u64, boolean: bool) -> Foo {
Foo {
bar: number,

View file

@ -11,12 +11,28 @@ struct Type2 {
z: bool,
}
struct Type3 {
a: b256,
b: u8,
}
impl Type3 {
// a constructor that evaluates to a constant during compilation
fn default() -> Self {
Self {
a: 0x0000000000000000000000000000000000000000000000000000000000000000,
b: 0,
}
}
}
storage {
var1: Type1 = Type1 { x: 0, y: 0 },
var2: Type2 = Type2 {
w: 0x0000000000000000000000000000000000000000000000000000000000000000,
z: false,
},
var3: Type3 = Type3::default(),
}
// ANCHOR_END: storage_declaration

View file

@ -1,22 +1,30 @@
// the _data_structures_ module
library;
// Declare a struct type
pub struct Foo {
bar: u64,
baz: bool,
pub bar: u64,
pub baz: bool,
}
// Struct types for destructuring
pub struct Point {
x: u64,
y: u64,
pub x: u64,
pub y: u64,
}
pub struct Line {
p1: Point,
p2: Point,
pub p1: Point,
pub p2: Point,
}
pub struct TupleInStruct {
nested_tuple: (u64, (u32, (bool, str))),
pub nested_tuple: (u64, (u32, (bool, str))),
}
// Struct type instantiable only in the module _data_structures_
pub struct StructWithPrivateFields {
pub public_field: u64,
private_field: u64,
other_private_field: u64,
}

View file

@ -9,7 +9,7 @@ license.workspace = true
repository.workspace = true
[dependencies]
annotate-snippets = { version = "0.9", features = ["color"] }
annotate-snippets = { version = "0.10.1" }
ansi_term = "0.12"
anyhow = "1"
clap = { version = "3.1", features = ["cargo", "derive", "env"] }

View file

@ -1,8 +1,8 @@
//! Utility items shared between forc crates.
use annotate_snippets::{
display_list::{DisplayList, FormatOptions},
snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
renderer::{AnsiColor, Style},
Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation,
};
use ansi_term::Colour;
use anyhow::{bail, Context, Result};
@ -434,6 +434,27 @@ pub fn print_on_failure(
}
}
/// Creates [Renderer] for printing warnings and errors.
///
/// To ensure the same styling of printed warnings and errors across all the tools,
/// always use this function to create [Renderer]s,
pub fn create_diagnostics_renderer() -> Renderer {
// For the diagnostic messages we use bold and bright colors.
// Note that for the summaries of warnings and errors we use
// their regular equivalents which are defined in `forc-tracing` package.
Renderer::styled()
.warning(
Style::new()
.bold()
.fg_color(Some(AnsiColor::BrightYellow.into())),
)
.error(
Style::new()
.bold()
.fg_color(Some(AnsiColor::BrightRed.into())),
)
}
fn format_diagnostic(diagnostic: &Diagnostic) {
/// Temporary switch for testing the feature.
/// Keep it false until we decide to fully support the diagnostic codes.
@ -482,15 +503,12 @@ fn format_diagnostic(diagnostic: &Diagnostic) {
title: snippet_title,
slices: snippet_slices,
footer: snippet_footer,
opt: FormatOptions {
color: true,
..Default::default()
},
};
let renderer = create_diagnostics_renderer();
match diagnostic.level() {
Level::Warning => tracing::warn!("{}\n____\n", DisplayList::from(snippet)),
Level::Error => tracing::error!("{}\n____\n", DisplayList::from(snippet)),
Level::Warning => tracing::warn!("{}\n____\n", renderer.render(snippet)),
Level::Error => tracing::error!("{}\n____\n", renderer.render(snippet)),
}
fn format_old_style_diagnostic(issue: &Issue) {
@ -535,13 +553,10 @@ fn format_diagnostic(diagnostic: &Diagnostic) {
title: snippet_title,
footer: vec![],
slices: snippet_slices,
opt: FormatOptions {
color: true,
..Default::default()
},
};
tracing::error!("{}\n____\n", DisplayList::from(snippet));
let renderer = create_diagnostics_renderer();
tracing::error!("{}\n____\n", renderer.render(snippet));
}
fn get_title_label(diagnostics: &Diagnostic, label: &mut String) {
@ -575,7 +590,7 @@ fn construct_slice(labels: Vec<&Label>) -> Slice {
"Slices can be constructed only for labels that are related to places in the same source code."
);
let soruce_file = labels[0].source_path().map(|path| path.as_str());
let source_file = labels[0].source_path().map(|path| path.as_str());
let source_code = labels[0].span().input();
// Joint span of the code snippet that covers all the labels.
@ -596,7 +611,7 @@ fn construct_slice(labels: Vec<&Label>) -> Slice {
return Slice {
source,
line_start,
origin: soruce_file,
origin: source_file,
fold: true,
annotations,
};
@ -637,7 +652,7 @@ fn label_type_to_annotation_type(label_type: LabelType) -> AnnotationType {
/// to show in the snippet.
///
/// Returns the source to be shown, the line start, and the offset of the snippet in bytes relative
/// to the begining of the input code.
/// to the beginning of the input code.
///
/// The library we use doesn't handle auto-windowing and line numbers, so we must manually
/// calculate the line numbers and match them up with the input window. It is a bit fiddly.

View file

@ -17,7 +17,7 @@ name = "forc"
path = "src/main.rs"
[dependencies]
annotate-snippets = { version = "0.9", features = ["color"] }
annotate-snippets = { version = "0.10.1" }
ansi_term = "0.12"
anyhow = "1.0.41"
clap = { version = "3.1", features = ["cargo", "derive", "env"] }

View file

@ -1,16 +1,13 @@
use anyhow::anyhow;
use clap::Parser;
use forc_util::ForcResult;
use forc_util::{create_diagnostics_renderer, ForcResult};
use std::collections::VecDeque;
use std::fs::{self, File};
use std::io::{self, prelude::*, BufReader};
use std::path::{Path, PathBuf};
use tracing::info;
use annotate_snippets::{
display_list::{DisplayList, FormatOptions},
snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},
};
use annotate_snippets::{AnnotationType, Slice, Snippet, SourceAnnotation};
use sway_core::source_map::{LocationRange, SourceMap};
@ -66,12 +63,10 @@ pub(crate) fn exec(command: Command) -> ForcResult<()> {
range: (rr.offset, rr.offset + rr.length),
}],
}],
opt: FormatOptions {
color: true,
..Default::default()
},
};
info!("{}", DisplayList::from(snippet));
let renderer = create_diagnostics_renderer();
info!("{}", renderer.render(snippet));
Ok(())
} else {

View file

@ -66,6 +66,7 @@ impl Spanned for ItemKind {
#[derive(Clone, Debug, Serialize)]
pub struct TypeField {
pub visibility: Option<PubToken>,
pub name: Ident,
pub colon_token: ColonToken,
pub ty: Ty,
@ -73,7 +74,12 @@ pub struct TypeField {
impl Spanned for TypeField {
fn span(&self) -> Span {
Span::join(self.name.span(), self.ty.span())
let start = match &self.visibility {
Some(pub_token) => pub_token.span(),
None => self.name.span(),
};
let end = self.ty.span();
Span::join(start, end)
}
}

View file

@ -1,6 +1,6 @@
//! Tools related to handling/recovering from Sway compile errors and reporting them to the user.
use crate::language::parsed::VariableDeclaration;
use crate::{language::parsed::VariableDeclaration, namespace::Path, Namespace};
/// Acts as the result of parsing `Declaration`s, `Expression`s, etc.
/// Some `Expression`s need to be able to create `VariableDeclaration`s,
@ -21,3 +21,21 @@ impl<T> ParserLifter<T> {
}
}
}
/// When providing suggestions for errors and warnings, a solution for an issue can sometimes
/// be changing the code in some other module. We want to provide such suggestions only if
/// the programmer can actually change the code in that module.
///
/// Assuming that the issue occurs in the `issue_namespace` to which the programmer has access,
/// and that fixing it means changing the code in the module given by the `absolute_module_path`
/// this function returns true if the programmer can change that module.
pub(crate) fn module_can_be_changed(
issue_namespace: &Namespace,
absolute_module_path: &Path,
) -> bool {
// For now, we assume that the programmers can change the module
// if the module is in the same package where the issue is.
// A bit too restrictive, considering the same workspace might be more appropriate,
// but it's a good start.
!issue_namespace.module_is_external(absolute_module_path)
}

View file

@ -1164,7 +1164,10 @@ mod tests {
let handler = Handler::default();
let mut context = Context::new(engines.se(), sway_ir::ExperimentalFlags::default());
let mut md_mgr = MetadataManager::default();
let core_lib = namespace::Module::default();
let mut core_lib = namespace::Module::default();
core_lib.name = Some(sway_types::Ident::new_no_span(
"assert_is_constant_test".to_string(),
));
let r = crate::compile_to_ast(
&handler,

View file

@ -99,7 +99,7 @@ pub(super) fn get_struct_name_field_index_and_type(
decl.fields
.iter()
.enumerate()
.find(|(_, field)| field.name == field_name.clone())
.find(|(_, field)| field.name == *field_name)
.map(|(idx, field)| (idx as u64, field.type_argument.type_id)),
))
}

View file

@ -348,7 +348,7 @@ impl CallPath {
// package name and the path to the current submodule.
//
// If the path starts with an external module (i.e. a module that is imported in
// `Forc.toml`, then do not change it since it's a complete path already.
// `Forc.toml`), then do not change it since it's a complete path already.
if m.is_external {
CallPath {
prefixes: self.prefixes.clone(),

View file

@ -13,6 +13,7 @@ pub struct StructDeclaration {
#[derive(Debug, Clone)]
pub struct StructField {
pub visibility: Visibility,
pub name: Ident,
pub attributes: transform::AttributesMap,
pub(crate) span: Span,

View file

@ -321,7 +321,6 @@ pub enum ReassignmentTarget {
pub struct StructExpressionField {
pub name: Ident,
pub value: Expression,
pub(crate) span: Span,
}
impl Spanned for Expression {

View file

@ -80,6 +80,38 @@ impl DebugWithEngines for TyFunctionDecl {
}
}
impl DisplayWithEngines for TyFunctionDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>, engines: &Engines) -> fmt::Result {
write!(
f,
"{}{}({}) -> {}",
self.name,
if !self.type_parameters.is_empty() {
format!(
"<{}>",
self.type_parameters
.iter()
.map(|p| format!("{}", engines.help_out(p.initial_type_id)))
.collect::<Vec<_>>()
.join(", ")
)
} else {
"".to_string()
},
self.parameters
.iter()
.map(|p| format!(
"{}: {}",
p.name.as_str(),
engines.help_out(p.type_argument.initial_type_id)
))
.collect::<Vec<_>>()
.join(", "),
engines.help_out(self.return_type.initial_type_id),
)
}
}
impl Named for TyFunctionDecl {
fn name(&self) -> &Ident {
&self.name
@ -377,6 +409,43 @@ impl TyFunctionDecl {
pub fn is_entry(&self) -> bool {
self.is_main_entry() || self.is_test()
}
/// Whether or not this function is a constructor for the type given by `type_id`.
///
/// Returns `Some(true)` if the function is surely the constructor and `Some(false)` if
/// it is surely not a constructor, and `None` if it cannot decide.
pub fn is_constructor(&self, engines: &Engines, type_id: TypeId) -> Option<bool> {
if self
.parameters
.first()
.map(|param| param.is_self())
.unwrap_or_default()
{
return Some(false);
};
match &self.implementing_type {
Some(TyDecl::ImplTrait(t)) => {
let unify_check = UnifyCheck::non_dynamic_equality(engines);
let implementing_for = engines.de().get(&t.decl_id).implementing_for.type_id;
// TODO: Implement the check in detail for all possible cases (e.g. trait impls for generics etc.)
// and return just the definite `bool` and not `Option<bool>`.
// That would be too much effort at the moment for the immediate practical need of
// error reporting where we suggest obvious most common constructors
// that will be found using this simple check.
if unify_check.check(type_id, implementing_for)
&& unify_check.check(type_id, self.return_type.type_id)
{
Some(true)
} else {
None
}
}
_ => Some(false),
}
}
}
#[derive(Debug, Clone)]

View file

@ -1,13 +1,18 @@
use std::hash::{Hash, Hasher};
use sway_error::{
error::CompileError,
error::{CompileError, StructFieldUsageContext},
handler::{ErrorEmitted, Handler},
warning::{CompileWarning, Warning},
};
use sway_types::{state::StateIndex, Ident, Named, Span, Spanned};
use crate::{
decl_engine::DeclEngine, engine_threading::*, language::ty::*, transform, type_system::*,
engine_threading::*,
language::{ty::*, Visibility},
transform,
type_system::*,
Namespace,
};
#[derive(Clone, Debug)]
@ -52,25 +57,41 @@ impl Spanned for TyStorageDecl {
}
impl TyStorageDecl {
/// Given a field, find its type information in the declaration and return it. If the field has not
/// been declared as a part of storage, return an error.
/// Given a path that consists of `fields`, where the first field is one of the storage fields,
/// find the type information of all the elements in the path and return it as a [TyStorageAccess].
///
/// The first element in the `fields` must be one of the storage fields.
/// The last element in the `fields` can, but must not be, a struct.
/// All the elements in between must be structs.
///
/// An error is returned if the above constraints are violated or if the access to the struct fields
/// fails. E.g, if the struct field does not exists or is an inaccessible private field.
pub fn apply_storage_load(
&self,
handler: &Handler,
type_engine: &TypeEngine,
decl_engine: &DeclEngine,
engines: &Engines,
namespace: &Namespace,
fields: Vec<Ident>,
storage_fields: &[TyStorageField],
storage_keyword_span: Span,
) -> Result<(TyStorageAccess, TypeId), ErrorEmitted> {
let mut type_checked_buf = vec![];
let mut fields: Vec<_> = fields.into_iter().rev().collect();
let type_engine = engines.te();
let decl_engine = engines.de();
// The resulting storage access descriptors, built on the go as we move through the `fields`.
let mut access_descriptors = vec![];
// The field we've analyzed before the current field we are on, and its type id.
let mut previous_field: &Ident;
let mut previous_field_type_id: TypeId;
let (first_field, remaining_fields) = fields.split_first().expect(
"Having at least one element in the storage load is guaranteed by the grammar.",
);
let first_field = fields.pop().expect("guaranteed by grammar");
let (ix, initial_field_type) = match storage_fields
.iter()
.enumerate()
.find(|(_, TyStorageField { name, .. })| name == &first_field)
.find(|(_, sf)| &sf.name == first_field)
{
Some((ix, TyStorageField { type_argument, .. })) => {
(StateIndex::new(ix), type_argument.type_id)
@ -83,57 +104,105 @@ impl TyStorageDecl {
}
};
type_checked_buf.push(TyStorageAccessDescriptor {
access_descriptors.push(TyStorageAccessDescriptor {
name: first_field.clone(),
type_id: initial_field_type,
span: first_field.span(),
});
let update_available_struct_fields = |id: TypeId| match &*type_engine.get(id) {
TypeInfo::Struct(decl_ref) => decl_engine.get_struct(decl_ref).fields.clone(),
_ => vec![],
previous_field = first_field;
previous_field_type_id = initial_field_type;
let get_struct_decl = |type_id: TypeId| match &*type_engine.get(type_id) {
TypeInfo::Struct(decl_ref) => Some(decl_engine.get_struct(decl_ref)),
_ => None,
};
// if the previously iterated type was a struct, put its fields here so we know that,
// in the case of a subfield, we can type check the that the subfield exists and its type.
let mut available_struct_fields = update_available_struct_fields(initial_field_type);
for field in remaining_fields {
match get_struct_decl(previous_field_type_id) {
Some(struct_decl) => {
let (struct_can_be_changed, is_public_struct_access) =
StructAccessInfo::get_info(&struct_decl, namespace).into();
// get the initial field's type
// make sure the next field exists in that type
for field in fields.into_iter().rev() {
match available_struct_fields
.iter()
.find(|x| x.name.as_str() == field.as_str())
{
Some(struct_field) => {
type_checked_buf.push(TyStorageAccessDescriptor {
name: field.clone(),
type_id: struct_field.type_argument.type_id,
span: field.span().clone(),
});
available_struct_fields =
update_available_struct_fields(struct_field.type_argument.type_id);
match struct_decl.find_field(field) {
Some(struct_field) => {
if is_public_struct_access && struct_field.is_private() {
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
// https://github.com/FuelLabs/sway/issues/5520
// return Err(handler.emit_err(CompileError::StructFieldIsPrivate {
// field_name: field.into(),
// struct_name: struct_decl.call_path.suffix.clone(),
// field_decl_span: struct_field.name.span(),
// struct_can_be_changed,
// usage_context: StructFieldUsageContext::StorageAccess,
// }));
handler.emit_warn(CompileWarning {
span: field.span(),
warning_content: Warning::StructFieldIsPrivate {
field_name: field.into(),
struct_name: struct_decl.call_path.suffix.clone(),
field_decl_span: struct_field.name.span(),
struct_can_be_changed,
usage_context: StructFieldUsageContext::StorageAccess,
},
});
}
// Everything is fine. Push the storage access descriptor and move to the next field.
let current_field_type_id = struct_field.type_argument.type_id;
access_descriptors.push(TyStorageAccessDescriptor {
name: field.clone(),
type_id: current_field_type_id,
span: field.span(),
});
previous_field = field;
previous_field_type_id = current_field_type_id;
}
None => {
// Since storage cannot be passed to other modules, the access
// is always in the module of the storage declaration.
// If the struct cannot be instantiated in this module at all,
// we will just show the error, without any additional help lines
// showing available fields or anything.
// Note that if the struct is empty it can always be instantiated.
let struct_can_be_instantiated =
!is_public_struct_access || !struct_decl.has_private_fields();
let available_fields = if struct_can_be_instantiated {
struct_decl.accessible_fields_names(is_public_struct_access)
} else {
vec![]
};
return Err(handler.emit_err(CompileError::StructFieldDoesNotExist {
field_name: field.into(),
available_fields,
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::StorageAccess,
}));
}
}
}
None => {
let available_fields = available_struct_fields
.iter()
.map(|x| x.name.as_str())
.collect::<Vec<_>>();
return Err(handler.emit_err(CompileError::FieldNotFound {
field_name: field.clone(),
available_fields: available_fields.join(", "),
struct_name: type_checked_buf.last().unwrap().name.clone(),
span: field.span(),
}));
return Err(handler.emit_err(CompileError::FieldAccessOnNonStruct {
span: previous_field.span(),
actually: engines.help_out(previous_field_type_id).to_string(),
}))
}
}
};
}
let return_type = type_checked_buf[type_checked_buf.len() - 1].type_id;
let return_type = access_descriptors[access_descriptors.len() - 1].type_id;
Ok((
TyStorageAccess {
fields: type_checked_buf,
fields: access_descriptors,
ix,
storage_keyword_span,
},
@ -152,6 +221,7 @@ impl TyStorageDecl {
ref attributes,
..
}| TyStructField {
visibility: Visibility::Public,
name: name.clone(),
span: span.clone(),
type_argument: type_argument.clone(),

View file

@ -3,18 +3,16 @@ use std::{
hash::{Hash, Hasher},
};
use sway_error::{
error::CompileError,
handler::{ErrorEmitted, Handler},
};
use sway_types::{Ident, Named, Span, Spanned};
use crate::{
engine_threading::*,
error::module_can_be_changed,
language::{CallPath, Visibility},
semantic_analysis::type_check_context::MonomorphizeHelper,
transform,
type_system::*,
Namespace,
};
#[derive(Clone, Debug)]
@ -94,31 +92,18 @@ impl MonomorphizeHelper for TyStructDecl {
}
impl TyStructDecl {
pub(crate) fn expect_field(
&self,
handler: &Handler,
field_to_access: &Ident,
) -> Result<&TyStructField, ErrorEmitted> {
match self
.fields
.iter()
.find(|TyStructField { name, .. }| name.as_str() == field_to_access.as_str())
{
Some(field) => Ok(field),
None => {
return Err(handler.emit_err(CompileError::FieldNotFound {
available_fields: self
.fields
.iter()
.map(|TyStructField { name, .. }| name.to_string())
.collect::<Vec<_>>()
.join("\n"),
field_name: field_to_access.clone(),
struct_name: self.call_path.suffix.clone(),
span: field_to_access.span(),
}));
}
}
/// Returns names of the [TyStructField]s of the struct `self` accessible in the given context.
/// If `is_public_struct_access` is true, only the names of the public fields are returned, otherwise
/// the names of all fields.
/// Suitable for error reporting.
pub(crate) fn accessible_fields_names(&self, is_public_struct_access: bool) -> Vec<Ident> {
TyStructField::accessible_fields_names(&self.fields, is_public_struct_access)
}
/// Returns [TyStructField] with the given `field_name`, or `None` if the field with the
/// name `field_name` does not exist.
pub(crate) fn find_field(&self, field_name: &Ident) -> Option<&TyStructField> {
self.fields.iter().find(|field| field.name == *field_name)
}
/// For the given `field_name` returns the zero-based index and the type of the field
@ -134,6 +119,106 @@ impl TyStructDecl {
.find(|(_, field)| field.name == *field_name)
.map(|(idx, field)| (idx as u64, field.type_argument.type_id))
}
/// Returns true if the struct `self` has at least one private field.
pub(crate) fn has_private_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_private())
}
/// Returns true if the struct `self` has fields (it is not empty)
/// and all fields are private.
pub(crate) fn has_only_private_fields(&self) -> bool {
!self.is_empty() && self.fields.iter().all(|field| field.is_private())
}
/// Returns true if the struct `self` does not have any fields.
pub(crate) fn is_empty(&self) -> bool {
self.fields.is_empty()
}
}
/// Provides information about the struct access within a particular [Namespace].
pub struct StructAccessInfo {
/// True if the programmer who can change the code in the [Namespace]
/// can also change the struct declaration.
struct_can_be_changed: bool,
/// True if the struct access is public, i.e., outside of the module in
/// which the struct is defined.
is_public_struct_access: bool,
}
impl StructAccessInfo {
pub fn get_info(struct_decl: &TyStructDecl, namespace: &Namespace) -> Self {
assert!(
struct_decl.call_path.is_absolute,
"The call path of the struct declaration must always be absolute."
);
let struct_can_be_changed =
module_can_be_changed(namespace, &struct_decl.call_path.prefixes);
let is_public_struct_access =
!namespace.module_is_submodule_of(&struct_decl.call_path.prefixes, true);
Self {
struct_can_be_changed,
is_public_struct_access,
}
}
}
impl From<StructAccessInfo> for (bool, bool) {
/// Deconstructs `struct_access_info` into (`struct_can_be_changed`, `is_public_struct_access`)
fn from(struct_access_info: StructAccessInfo) -> (bool, bool) {
let StructAccessInfo {
struct_can_be_changed,
is_public_struct_access,
} = struct_access_info;
(struct_can_be_changed, is_public_struct_access)
}
}
#[derive(Debug, Clone)]
pub struct TyStructField {
pub visibility: Visibility,
pub name: Ident,
pub span: Span,
pub type_argument: TypeArgument,
pub attributes: transform::AttributesMap,
}
impl TyStructField {
pub fn is_private(&self) -> bool {
matches!(self.visibility, Visibility::Private)
}
pub fn is_public(&self) -> bool {
matches!(self.visibility, Visibility::Public)
}
/// Returns [TyStructField]s from the `fields` that are accessible in the given context.
/// If `is_public_struct_access` is true, only public fields are returned, otherwise
/// all fields.
pub(crate) fn accessible_fields(
fields: &[TyStructField],
is_public_struct_access: bool,
) -> impl Iterator<Item = &TyStructField> {
fields
.iter()
.filter(move |field| !is_public_struct_access || field.is_public())
}
/// Returns names of the [TyStructField]s from the `fields` that are accessible in the given context.
/// If `is_public_struct_access` is true, only the names of the public fields are returned, otherwise
/// the names of all fields.
/// Suitable for error reporting.
pub(crate) fn accessible_fields_names(
fields: &[TyStructField],
is_public_struct_access: bool,
) -> Vec<Ident> {
Self::accessible_fields(fields, is_public_struct_access)
.map(|field| field.name.clone())
.collect()
}
}
impl Spanned for TyStructField {
@ -142,17 +227,10 @@ impl Spanned for TyStructField {
}
}
#[derive(Debug, Clone)]
pub struct TyStructField {
pub name: Ident,
pub span: Span,
pub type_argument: TypeArgument,
pub attributes: transform::AttributesMap,
}
impl HashWithEngines for TyStructField {
fn hash<H: Hasher>(&self, state: &mut H, engines: &Engines) {
let TyStructField {
visibility,
name,
type_argument,
// these fields are not hashed because they aren't relevant/a
@ -160,6 +238,7 @@ impl HashWithEngines for TyStructField {
span: _,
attributes: _,
} = self;
visibility.hash(state);
name.hash(state);
type_argument.hash(state, engines);
}
@ -177,18 +256,18 @@ impl OrdWithEngines for TyStructField {
let TyStructField {
name: ln,
type_argument: lta,
// these fields are not compared because they aren't relevant/a
// reliable source of obj v. obj distinction
// these fields are not compared because they aren't relevant for ordering
span: _,
attributes: _,
visibility: _,
} = self;
let TyStructField {
name: rn,
type_argument: rta,
// these fields are not compared because they aren't relevant/a
// reliable source of obj v. obj distinction
// these fields are not compared because they aren't relevant for ordering
span: _,
attributes: _,
visibility: _,
} = other;
ln.cmp(rn).then_with(|| lta.cmp(rta, engines))
}

View file

@ -334,7 +334,10 @@ impl TyDecl {
None,
)?;
let mut ctx = ctx.by_ref().with_type_annotation(type_argument.type_id);
let mut ctx = ctx
.by_ref()
.with_type_annotation(type_argument.type_id)
.with_storage_declaration();
let initializer =
ty::TyExpression::type_check(handler, ctx.by_ref(), initializer)?;

View file

@ -73,6 +73,7 @@ impl ty::TyStructField {
type_engine.insert(ctx.engines(), TypeInfo::ErrorRecovery(err), None)
});
let field = ty::TyStructField {
visibility: field.visibility,
name: field.name,
span: field.span,
type_argument,

View file

@ -439,6 +439,7 @@ fn match_struct(
let subfield = instantiate_struct_field_access(
handler,
ctx.engines(),
ctx.namespace,
exp.clone(),
field.clone(),
field_span,

View file

@ -1,6 +1,8 @@
use itertools::Itertools;
use sway_error::{
error::CompileError,
error::{CompileError, StructFieldUsageContext},
handler::{ErrorEmitted, Handler},
warning::{CompileWarning, Warning},
};
use sway_types::{BaseIdent, Ident, Span, Spanned};
@ -8,7 +10,7 @@ use crate::{
decl_engine::DeclEngineInsert,
language::{
parsed::*,
ty::{self, TyDecl, TyScrutinee},
ty::{self, StructAccessInfo, TyDecl, TyScrutinee, TyStructDecl, TyStructField},
CallPath,
},
semantic_analysis::{
@ -233,55 +235,230 @@ fn type_check_struct(
&struct_name.span(),
)?;
// type check the fields
let (struct_can_be_changed, is_public_struct_access) =
StructAccessInfo::get_info(&struct_decl, ctx.namespace).into();
let has_rest_pattern = fields
.iter()
.any(|field| matches!(field, StructScrutineeField::Rest { .. }));
// check for field existence and type check nested scrutinees; short-circuit if there are non-existing fields
// TODO: Is short-circuiting really needed or was it more a convenience? In the first implementation
// we had a short-circuit on the first error non-existing field and didn't even collecting all errors.
let mut typed_fields = vec![];
let mut rest_pattern = None;
for field in fields.into_iter() {
match field {
StructScrutineeField::Rest { .. } => rest_pattern = Some(field),
StructScrutineeField::Field {
field,
scrutinee,
span,
} => {
// ensure that the struct definition has this field
let struct_field = struct_decl.expect_field(handler, &field)?;
// type check the nested scrutinee
let typed_scrutinee = match scrutinee {
None => None,
Some(scrutinee) => Some(ty::TyScrutinee::type_check(
handler,
ctx.by_ref(),
scrutinee,
)?),
};
typed_fields.push(ty::TyStructScrutineeField {
handler.scope(|handler| {
for field in fields.iter() {
match field {
StructScrutineeField::Field {
field,
scrutinee: typed_scrutinee,
scrutinee,
span,
field_def_name: struct_field.name.clone(),
});
} => {
// ensure that the struct definition has this field
let struct_field = match expect_struct_field(
&struct_decl,
handler,
field,
has_rest_pattern,
is_public_struct_access,
) {
Ok(struct_field) => struct_field,
Err(_) => continue,
};
// type check the nested scrutinee
let typed_scrutinee = match scrutinee {
None => None,
Some(scrutinee) => Some(ty::TyScrutinee::type_check(
handler,
ctx.by_ref(),
scrutinee.clone(),
)?),
};
typed_fields.push(ty::TyStructScrutineeField {
field: field.clone(),
scrutinee: typed_scrutinee,
span: span.clone(),
field_def_name: struct_field.name.clone(),
});
}
StructScrutineeField::Rest { .. } => {}
}
}
}
// ensure that the pattern uses all fields of the struct unless the rest pattern is present
if (struct_decl.fields.len() != typed_fields.len()) && rest_pattern.is_none() {
let missing_fields = struct_decl
.fields
.iter()
.filter_map(|f| {
(!typed_fields.iter().any(|tf| f.name == tf.field)).then_some(f.name.to_string())
})
.collect::<Vec<_>>();
Ok(())
})?;
return Err(
handler.emit_err(CompileError::MatchStructPatternMissingFields {
span,
missing_fields,
}),
);
}
handler.scope(|handler| {
// report struct field privacy errors
// This check is intentionally separated from checking the field existence and type-checking the scrutinees.
// While we could check private field access immediately after finding the field and emit errors,
// that would mean short-circuiting in case of privacy issues which we do not want to do.
// The consequence is repeating the search for fields here, but the performance penalty is negligible.
if is_public_struct_access {
for field in fields {
match field {
StructScrutineeField::Field {
field: ref field_name,
..
} => {
let struct_field = struct_decl
.find_field(field_name)
.expect("The struct field with the given field name must exist.");
if struct_field.is_private() {
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
// https://github.com/FuelLabs/sway/issues/5520
// 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::PatternMatching { has_rest_pattern },
// });
handler.emit_warn(CompileWarning {
span: field_name.span(),
warning_content: Warning::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::PatternMatching {
has_rest_pattern,
},
},
});
}
}
StructScrutineeField::Rest { .. } => {}
}
}
}
// ensure that the pattern uses all fields of the struct unless the rest pattern is present
// Here we follow the approach Rust has, and show a dedicated error if only all public fields are
// listed, but the mandatory `..` (because of the private fields) is missing because the struct
// has private fields and is used outside of its decl module.
// Also, in case of privacy issues and mixing public and private fields we list only the public
// fields as missing.
// The error message in both cases gives adequate explanation how to fix the reported issue.
if !has_rest_pattern && (struct_decl.fields.len() != typed_fields.len()) {
let all_public_fields_are_matched = struct_decl
.fields
.iter()
.filter(|f| f.is_public())
.all(|f| typed_fields.iter().any(|tf| f.name == tf.field));
let only_public_fields_are_matched = typed_fields
.iter()
.map(|tf| {
struct_decl
.find_field(&tf.field)
.expect("The struct field with the given field name must exist.")
})
.all(|f| f.is_public());
// In the case of public access where all public fields are listed along with some private fields,
// we already have an error emitted for those private fields with the detailed, pattern matching related
// explanation that proposes using ignore `..`.
if !(is_public_struct_access
&& all_public_fields_are_matched
&& !only_public_fields_are_matched)
{
let missing_fields = |only_public: bool| {
struct_decl
.fields
.iter()
.filter(|f| !only_public || f.is_public())
.filter(|f| !typed_fields.iter().any(|tf| f.name == tf.field))
.map(|field| field.name.clone())
.collect_vec()
};
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
// https://github.com/FuelLabs/sway/issues/5520
// handler.emit_err(
// match (is_public_struct_access, all_public_fields_are_matched, only_public_fields_are_matched) {
// // Public access. Only all public fields are matched. All missing fields are private.
// // -> Emit error for the mandatory ignore `..`.
// (true, true, true) => CompileError::MatchStructPatternMustIgnorePrivateFields {
// private_fields: missing_fields(false),
// struct_name: struct_decl.call_path.suffix.clone(),
// struct_decl_span: struct_decl.span(),
// all_fields_are_private: struct_decl.has_only_private_fields(),
// span: span.clone(),
// },
// // Public access. All public fields are matched. Some private fields are matched.
// // -> Do not emit error here because it is already covered when reporting private field.
// (true, true, false) => unreachable!("The above if condition eliminates this case."),
// // Public access. Some or non of the public fields are matched. Some or none of the private fields are matched.
// // -> Emit error listing only missing public fields. Recommendation for mandatory use of `..` is already given
// // when reporting the inaccessible private field.
// // or
// // In struct decl module access. We do not distinguish between private and public fields here.
// // -> Emit error listing all missing fields.
// (true, false, _) | (false, _, _) => CompileError::MatchStructPatternMissingFields {
// missing_fields: missing_fields(is_public_struct_access),
// missing_fields_are_public: is_public_struct_access,
// struct_name: struct_decl.call_path.suffix.clone(),
// struct_decl_span: struct_decl.span(),
// total_number_of_fields: struct_decl.fields.len(),
// span: span.clone(),
// },
// });
match (
is_public_struct_access,
all_public_fields_are_matched,
only_public_fields_are_matched,
) {
// Public access. Only all public fields are matched. All missing fields are private.
// -> Emit error for the mandatory ignore `..`.
(true, true, true) => {
handler.emit_warn(CompileWarning {
span: span.clone(),
warning_content: Warning::MatchStructPatternMustIgnorePrivateFields {
private_fields: missing_fields(false),
struct_name: struct_decl.call_path.suffix.clone(),
struct_decl_span: struct_decl.span(),
all_fields_are_private: struct_decl.has_only_private_fields(),
span: span.clone(),
},
});
}
// Public access. All public fields are matched. Some private fields are matched.
// -> Do not emit error here because it is already covered when reporting private field.
(true, true, false) => {
unreachable!("The above if condition eliminates this case.")
}
// Public access. Some or non of the public fields are matched. Some or none of the private fields are matched.
// -> Emit error listing only missing public fields. Recommendation for mandatory use of `..` is already given
// when reporting the inaccessible private field.
// or
// In struct decl module access. We do not distinguish between private and public fields here.
// -> Emit error listing all missing fields.
(true, false, _) | (false, _, _) => {
handler.emit_err(CompileError::MatchStructPatternMissingFields {
missing_fields: missing_fields(is_public_struct_access),
missing_fields_are_public: is_public_struct_access,
struct_name: struct_decl.call_path.suffix.clone(),
struct_decl_span: struct_decl.span(),
total_number_of_fields: struct_decl.fields.len(),
span: span.clone(),
});
}
};
}
}
Ok(())
})?;
let struct_ref = decl_engine.insert(struct_decl);
let typed_scrutinee = ty::TyScrutinee {
@ -302,7 +479,28 @@ fn type_check_struct(
},
};
Ok(typed_scrutinee)
return Ok(typed_scrutinee);
fn expect_struct_field<'a>(
struct_decl: &'a TyStructDecl,
handler: &Handler,
field_name: &Ident,
has_rest_pattern: bool,
is_public_struct_access: bool,
) -> Result<&'a TyStructField, ErrorEmitted> {
match struct_decl.find_field(field_name) {
Some(field) => Ok(field),
None => 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::PatternMatching { has_rest_pattern },
})),
}
}
}
impl TypeCheckFinalization for TyScrutinee {

View file

@ -924,11 +924,18 @@ impl ty::TyExpression {
let type_engine = ctx.engines.te();
let engines = ctx.engines();
let ctx = ctx
let mut ctx = ctx
.with_help_text("")
.with_type_annotation(type_engine.insert(engines, TypeInfo::Unknown, None));
let parent = ty::TyExpression::type_check(handler, ctx, prefix)?;
let exp = instantiate_struct_field_access(handler, engines, parent, field_to_access, span)?;
let parent = ty::TyExpression::type_check(handler, ctx.by_ref(), prefix)?;
let exp = instantiate_struct_field_access(
handler,
engines,
ctx.namespace,
parent,
field_to_access,
span,
)?;
Ok(exp)
}
@ -1018,6 +1025,7 @@ impl ty::TyExpression {
let (storage_access, mut access_type) = ctx.namespace.apply_storage_load(
handler,
ctx.engines,
ctx.namespace,
checkee,
&storage_fields,
storage_keyword_span,
@ -1993,6 +2001,7 @@ impl ty::TyExpression {
let (ty_of_field, _ty_of_parent) = ctx.namespace.find_subfield_type(
handler,
ctx.engines(),
ctx.namespace,
&base_name,
&names_vec,
)?;

View file

@ -1,11 +1,12 @@
use sway_error::handler::{ErrorEmitted, Handler};
use sway_types::{Ident, Span, Spanned};
use crate::{language::ty, Engines};
use crate::{language::ty, Engines, Namespace};
pub(crate) fn instantiate_struct_field_access(
handler: &Handler,
engines: &Engines,
namespace: &Namespace,
parent: ty::TyExpression,
field_to_access: Ident,
span: Span,
@ -15,6 +16,7 @@ pub(crate) fn instantiate_struct_field_access(
let field = type_engine.get(parent.return_type).apply_subfields(
handler,
engines,
namespace,
&[field_to_access],
&parent.span,
)?;

View file

@ -1,16 +1,23 @@
use itertools::Itertools;
use sway_error::{
error::CompileError,
error::{CompileError, StructFieldUsageContext},
handler::{ErrorEmitted, Handler},
warning::{CompileWarning, Warning},
};
use sway_types::{Ident, Span, Spanned};
use crate::{
decl_engine::DeclRefStruct,
language::{parsed::*, ty, CallPath},
language::{
parsed::*,
ty::{self, StructAccessInfo, TyStructField},
CallPath, Visibility,
},
semantic_analysis::{
type_check_context::EnforceTypeArguments, GenericShadowingMode, TypeCheckContext,
},
type_system::*,
Namespace,
};
const UNIFY_STRUCT_FIELD_HELP_TEXT: &str =
@ -92,10 +99,22 @@ pub(crate) fn struct_instantiation(
let type_info = type_engine.get(type_id);
let struct_ref = type_info.expect_struct(handler, engines, &span)?;
let struct_decl = (*decl_engine.get_struct(&struct_ref)).clone();
let (struct_can_be_changed, is_public_struct_access) =
StructAccessInfo::get_info(&struct_decl, ctx.namespace).into();
let struct_has_private_fields = struct_decl.has_private_fields();
let struct_can_be_instantiated = !is_public_struct_access || !struct_has_private_fields;
let all_fields_are_private = struct_decl.has_only_private_fields();
let struct_is_empty = struct_decl.is_empty();
let struct_name = struct_decl.call_path.suffix;
let struct_fields = struct_decl.fields;
let mut struct_fields = struct_fields;
// To avoid conflicting and overlapping errors, we follow the Rust approach:
// - Missing fields are reported only if the struct can actually be instantiated.
// - Individual fields issues are always reported: private field access, non-existing fields.
let typed_fields = type_check_field_arguments(
handler,
ctx.by_ref(),
@ -103,8 +122,50 @@ pub(crate) fn struct_instantiation(
&struct_name,
&mut struct_fields,
&span,
&struct_decl.span,
// Emit the missing fields error only if the struct can actually be instantiated.
struct_can_be_instantiated,
)?;
if !struct_can_be_instantiated {
let constructors = collect_struct_constructors(
ctx.namespace,
ctx.engines,
type_id,
ctx.storage_declaration(),
);
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
// https://github.com/FuelLabs/sway/issues/5520
// handler.emit_err(CompileError::StructCannotBeInstantiated {
// struct_name: struct_name.clone(),
// span: inner_span.clone(),
// struct_decl_span: struct_decl.span.clone(),
// private_fields: struct_fields.iter().filter(|field| field.is_private()).map(|field| field.name.clone()).collect(),
// constructors,
// all_fields_are_private,
// is_in_storage_declaration: ctx.storage_declaration(),
// struct_can_be_changed,
// });
handler.emit_warn(CompileWarning {
span: inner_span.clone(),
warning_content: Warning::StructCannotBeInstantiated {
struct_name: struct_name.clone(),
span: inner_span.clone(),
struct_decl_span: struct_decl.span.clone(),
private_fields: struct_fields
.iter()
.filter(|field| field.is_private())
.map(|field| field.name.clone())
.collect(),
constructors,
all_fields_are_private,
is_in_storage_declaration: ctx.storage_declaration(),
struct_can_be_changed,
},
});
}
unify_field_arguments_and_struct_fields(handler, ctx.by_ref(), &typed_fields, &struct_fields)?;
// Unify type id with type annotation so eventual generic type parameters are properly resolved.
@ -119,17 +180,74 @@ pub(crate) fn struct_instantiation(
None,
);
// check that there are no extra fields
for field in fields {
// Check that there are no extra fields.
for field in fields.iter() {
if !struct_fields.iter().any(|x| x.name == field.name) {
handler.emit_err(CompileError::StructDoesNotHaveField {
field_name: field.name.clone(),
handler.emit_err(CompileError::StructFieldDoesNotExist {
field_name: (&field.name).into(), // Explicit borrow to force the `From<&BaseIdent>` instead of `From<BaseIdent>`.
available_fields: TyStructField::accessible_fields_names(
&struct_fields,
is_public_struct_access,
),
is_public_struct_access,
struct_name: struct_name.clone(),
span: field.span,
struct_decl_span: struct_decl.span.clone(),
struct_is_empty,
usage_context: if ctx.storage_declaration() {
StructFieldUsageContext::StorageDeclaration {
struct_can_be_instantiated,
}
} else {
StructFieldUsageContext::StructInstantiation {
struct_can_be_instantiated,
}
},
});
}
}
// If the current module being checked is not a submodule of the
// module in which the struct is declared, check for private fields usage.
if is_public_struct_access {
for field in fields {
if let Some(ty_field) = struct_fields.iter().find(|x| x.name == field.name) {
if ty_field.is_private() {
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
// https://github.com/FuelLabs/sway/issues/5520
// handler.emit_err(CompileError::StructFieldIsPrivate {
// field_name: (&field.name).into(),
// struct_name: struct_name.clone(),
// field_decl_span: ty_field.name.span(),
// struct_can_be_changed,
// usage_context: if ctx.storage_declaration() {
// StructFieldUsageContext::StorageDeclaration { struct_can_be_instantiated }
// } else {
// StructFieldUsageContext::StructInstantiation { struct_can_be_instantiated }
// }
// });
handler.emit_warn(CompileWarning {
span: field.name.span(),
warning_content: Warning::StructFieldIsPrivate {
field_name: (&field.name).into(),
struct_name: struct_name.clone(),
field_decl_span: ty_field.name.span(),
struct_can_be_changed,
usage_context: if ctx.storage_declaration() {
StructFieldUsageContext::StorageDeclaration {
struct_can_be_instantiated,
}
} else {
StructFieldUsageContext::StructInstantiation {
struct_can_be_instantiated,
}
},
},
});
}
}
}
}
let mut struct_namespace = ctx.namespace.clone();
let mut struct_ctx = ctx
.scoped(&mut struct_namespace)
@ -154,10 +272,55 @@ pub(crate) fn struct_instantiation(
span,
};
Ok(exp)
return Ok(exp);
fn collect_struct_constructors(
namespace: &Namespace,
engines: &crate::Engines,
struct_type_id: TypeId,
is_in_storage_declaration: bool,
) -> Vec<String> {
// Searching only for public constructors is a bit too restrictive because we can also have them in local private impls.
// Checking that would be a questionable additional effort considering that this search gives good suggestions for
// common patterns in which constructors can be found.
// Also, strictly speaking, we could also have public module functions that create structs,
// 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
.get_items_for_type(engines, struct_type_id)
.iter()
.filter_map(|item| match item {
ty::TyTraitItem::Fn(fn_decl_id) => Some(fn_decl_id),
_ => None,
})
.map(|fn_decl_id| engines.de().get_function(fn_decl_id))
.filter(|fn_decl| {
matches!(fn_decl.visibility, Visibility::Public)
&& fn_decl
.is_constructor(engines, struct_type_id)
.unwrap_or_default()
// For suggestions in storage declarations, we go for the simplest heuristics possible -
// returning only parameterless constructors. Doing the const evaluation here would be
// a questionable additional effort considering that this simple heuristics will give
// us all the most common constructors like `default()` or `new()`.
&& (!is_in_storage_declaration || fn_decl.parameters.is_empty())
})
.map(|fn_decl| {
// Removing the return type from the signature by searching for last `->` will work as long as we don't have something like `Fn`.
format!("{}", engines.help_out((*fn_decl).clone()))
.rsplit_once(" -> ")
.unwrap()
.0
.to_string()
})
.sorted()
.dedup()
.collect_vec()
}
}
/// Type checks the field arguments.
#[allow(clippy::too_many_arguments)]
fn type_check_field_arguments(
handler: &Handler,
mut ctx: TypeCheckContext,
@ -165,11 +328,14 @@ fn type_check_field_arguments(
struct_name: &Ident,
struct_fields: &mut [ty::TyStructField],
span: &Span,
struct_decl_span: &Span,
emit_missing_fields_error: bool,
) -> Result<Vec<ty::TyStructExpressionField>, ErrorEmitted> {
let type_engine = ctx.engines.te();
let engines = ctx.engines();
let mut typed_fields = vec![];
let mut missing_fields = vec![];
for struct_field in struct_fields.iter_mut() {
match fields.iter().find(|x| x.name == struct_field.name) {
@ -190,11 +356,16 @@ fn type_check_field_arguments(
struct_field.span = field.value.span.clone();
}
None => {
let err = handler.emit_err(CompileError::StructMissingField {
field_name: struct_field.name.clone(),
struct_name: struct_name.clone(),
span: span.clone(),
});
missing_fields.push(struct_field.name.clone());
let err = Handler::default().emit_err(
CompileError::StructInstantiationMissingFieldForErrorRecovery {
field_name: struct_field.name.clone(),
struct_name: struct_name.clone(),
span: span.clone(),
},
);
typed_fields.push(ty::TyStructExpressionField {
name: struct_field.name.clone(),
value: ty::TyExpression {
@ -211,6 +382,16 @@ fn type_check_field_arguments(
}
}
if emit_missing_fields_error && !missing_fields.is_empty() {
handler.emit_err(CompileError::StructInstantiationMissingFields {
field_names: missing_fields,
struct_name: struct_name.clone(),
span: span.clone(),
struct_decl_span: struct_decl_span.clone(),
total_number_of_fields: struct_fields.len(),
});
}
Ok(typed_fields)
}

View file

@ -2,7 +2,7 @@ use crate::{
decl_engine::*,
engine_threading::Engines,
language::{
ty::{self, TyDecl, TyStorageDecl},
ty::{self, StructAccessInfo, TyDecl, TyStorageDecl},
CallPath,
},
namespace::*,
@ -13,8 +13,9 @@ use crate::{
use super::TraitMap;
use sway_error::{
error::CompileError,
error::{CompileError, StructFieldUsageContext},
handler::{ErrorEmitted, Handler},
warning::{CompileWarning, Warning},
};
use sway_types::{span::Span, Spanned};
@ -62,19 +63,18 @@ impl Items {
&self,
handler: &Handler,
engines: &Engines,
namespace: &Namespace,
fields: Vec<Ident>,
storage_fields: &[ty::TyStorageField],
storage_keyword_span: Span,
) -> Result<(ty::TyStorageAccess, TypeId), ErrorEmitted> {
let type_engine = engines.te();
let decl_engine = engines.de();
match self.declared_storage {
Some(ref decl_ref) => {
let storage = decl_engine.get_storage(&decl_ref.id().clone());
let storage = engines.de().get_storage(&decl_ref.id().clone());
storage.apply_storage_load(
handler,
type_engine,
decl_engine,
engines,
namespace,
fields,
storage_fields,
storage_keyword_span,
@ -328,12 +328,13 @@ impl Items {
}
}
/// Returns a tuple where the first element is the [ResolvedType] of the actual expression, and
/// the second is the [ResolvedType] of its parent, for control-flow analysis.
/// Returns a tuple where the first element is the [TypeId] of the actual expression, and
/// the second is the [TypeId] of its parent.
pub(crate) fn find_subfield_type(
&self,
handler: &Handler,
engines: &Engines,
namespace: &Namespace,
base_name: &Ident,
projections: &[ty::ProjectionKind],
) -> Result<(TypeId, TypeId), ErrorEmitted> {
@ -367,41 +368,49 @@ impl Items {
ty::ProjectionKind::StructField { name: field_name },
) => {
let struct_decl = decl_engine.get_struct(&decl_ref);
let field_type_opt = {
struct_decl.fields.iter().find_map(
|ty::TyStructField {
type_argument,
name,
..
}| {
if name == field_name {
Some(type_argument.type_id)
} else {
None
}
},
)
};
let field_type = match field_type_opt {
Some(field_type) => field_type,
None => {
// gather available fields for the error message
let available_fields = struct_decl
.fields
.iter()
.map(|field| field.name.as_str())
.collect::<Vec<_>>();
let (struct_can_be_changed, is_public_struct_access) =
StructAccessInfo::get_info(&struct_decl, namespace).into();
return Err(handler.emit_err(CompileError::FieldNotFound {
field_name: field_name.clone(),
let field_type_id = match struct_decl.find_field(field_name) {
Some(struct_field) => {
if is_public_struct_access && struct_field.is_private() {
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
// https://github.com/FuelLabs/sway/issues/5520
// 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,
// }));
handler.emit_warn(CompileWarning {
span: field_name.span(),
warning_content: Warning::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(),
available_fields: available_fields.join(", "),
span: field_name.span(),
struct_decl_span: struct_decl.span(),
struct_is_empty: struct_decl.is_empty(),
usage_context: StructFieldUsageContext::StructFieldAccess,
}));
}
};
parent_rover = symbol;
symbol = field_type;
symbol = field_type_id;
symbol_span = field_name.span().clone();
full_name_for_error.push_str(field_name.as_str());
full_span_for_error =

View file

@ -162,7 +162,7 @@ impl Module {
// get the decl out of the typed node:
// we know as an invariant this must be a const decl, as we hardcoded a const decl in
// the above `format!`. if it isn't we report an
// error that only constant items are alowed, defensive programming etc...
// error that only constant items are allowed, defensive programming etc...
let typed_decl = match typed_node.content {
ty::TyAstNodeContent::Declaration(decl) => decl,
_ => {

View file

@ -145,6 +145,68 @@ impl Namespace {
parent_mod_path,
}
}
/// Returns true if the current module being checked is a direct or indirect submodule of
/// the module given by the `absolute_module_path`.
///
/// The current module being checked is determined by `mod_path`.
///
/// E.g., the `mod_path` `[fist, second, third]` of the root `foo` is a submodule of the module
/// `[foo, first]`. Note that the `mod_path` does not contain the root name, while the
/// `absolute_module_path` always contains it.
///
/// If the current module being checked is the same as the module given by the `absolute_module_path`,
/// the `true_if_same` is returned.
pub(crate) fn module_is_submodule_of(
&self,
absolute_module_path: &Path,
true_if_same: bool,
) -> bool {
// `mod_path` does not contain the root name, so we have to separately check
// that the root name is equal to the module package name.
let root_name = match &self.root.name {
Some(name) => name,
None => panic!("Root module must always have a name."),
};
let (package_name, modules) = absolute_module_path.split_first().expect("Absolute module path must have at least one element, because it always contains the package name.");
if root_name != package_name {
return false;
}
if self.mod_path.len() < modules.len() {
return false;
}
let is_submodule = modules
.iter()
.zip(self.mod_path.iter())
.all(|(left, right)| left == right);
if is_submodule {
if self.mod_path.len() == modules.len() {
true_if_same
} else {
true
}
} else {
false
}
}
/// Returns true if the module given by the `absolute_module_path` is external
/// to the current package. External modules are imported in the `Forc.toml` file.
pub(crate) fn module_is_external(&self, absolute_module_path: &Path) -> bool {
let root_name = match &self.root.name {
Some(name) => name,
None => panic!("Root module must always have a name."),
};
assert!(!absolute_module_path.is_empty(), "Absolute module path must have at least one element, because it always contains the package name.");
root_name != &absolute_module_path[0]
}
}
impl std::ops::Deref for Namespace {

View file

@ -93,6 +93,9 @@ pub struct TypeCheckContext<'a> {
/// after we perform a dependency analysis on the tree.
defer_monomorphization: bool,
/// Indicates when semantic analysis is type checking storage declaration.
storage_declaration: bool,
/// Set of experimental flags
pub experimental: ExperimentalFlags,
}
@ -126,6 +129,7 @@ impl<'a> TypeCheckContext<'a> {
kind: TreeType::Contract,
disallow_functions: false,
defer_monomorphization: false,
storage_declaration: false,
experimental: ExperimentalFlags::default(),
}
}
@ -154,6 +158,7 @@ impl<'a> TypeCheckContext<'a> {
engines: self.engines,
disallow_functions: self.disallow_functions,
defer_monomorphization: self.defer_monomorphization,
storage_declaration: self.storage_declaration,
experimental: self.experimental,
}
}
@ -175,6 +180,7 @@ impl<'a> TypeCheckContext<'a> {
engines: self.engines,
disallow_functions: self.disallow_functions,
defer_monomorphization: self.defer_monomorphization,
storage_declaration: self.storage_declaration,
experimental: self.experimental,
}
}
@ -191,8 +197,7 @@ impl<'a> TypeCheckContext<'a> {
with_submod_ctx: impl FnOnce(TypeCheckContext) -> T,
) -> T {
// We're checking a submodule, so no need to pass through anything other than the
// namespace. However, we will likely want to pass through the type engine and declaration
// engine here once they're added.
// namespace and the engines.
let Self { namespace, .. } = self;
let mut submod_ns = namespace.enter_submodule(mod_name, visibility, module_span);
let submod_ctx = TypeCheckContext::from_module_namespace(&mut submod_ns, self.engines);
@ -297,6 +302,15 @@ impl<'a> TypeCheckContext<'a> {
}
}
/// Map this `TypeCheckContext` instance to a new one with
/// `storage_declaration` set to `true`.
pub(crate) fn with_storage_declaration(self) -> Self {
Self {
storage_declaration: true,
..self
}
}
// A set of accessor methods. We do this rather than making the fields `pub` in order to ensure
// that these are only updated via the `with_*` methods that produce a new `TypeCheckContext`.
@ -349,6 +363,10 @@ impl<'a> TypeCheckContext<'a> {
self.defer_monomorphization
}
pub(crate) fn storage_declaration(&self) -> bool {
self.storage_declaration
}
// Provide some convenience functions around the inner context.
/// Short-hand for calling the `monomorphize` function in the type engine

View file

@ -1092,6 +1092,7 @@ fn type_field_to_struct_field(
) -> Result<StructField, ErrorEmitted> {
let span = type_field.span();
let struct_field = StructField {
visibility: pub_token_opt_to_visibility(type_field.visibility),
name: type_field.name,
attributes,
type_argument: ty_to_type_argument(context, handler, engines, type_field.ty)?,
@ -3227,13 +3228,12 @@ fn expr_struct_field_to_struct_expression_field(
Some((_colon_token, expr)) => expr_to_expression(context, handler, engines, *expr)?,
None => Expression {
kind: ExpressionKind::Variable(expr_struct_field.field_name.clone()),
span: span.clone(),
span,
},
};
Ok(StructExpressionField {
name: expr_struct_field.field_name,
value,
span,
})
}

View file

@ -1,13 +1,17 @@
use crate::{
decl_engine::{DeclEngine, DeclRefEnum, DeclRefStruct},
engine_threading::*,
language::{ty, CallPath, QualifiedCallPath},
language::{
ty::{self, StructAccessInfo},
CallPath, QualifiedCallPath,
},
type_system::priv_prelude::*,
Ident,
Ident, Namespace,
};
use sway_error::{
error::CompileError,
error::{CompileError, StructFieldUsageContext},
handler::{ErrorEmitted, Handler},
warning::{CompileWarning, Warning},
};
use sway_types::{integer_bits::IntegerBits, span::Span, SourceId, Spanned};
@ -1248,10 +1252,12 @@ impl TypeInfo {
/// 1) in the case where `self` is not a [TypeInfo::Struct]
/// 2) in the case where `subfields` is empty
/// 3) in the case where a `subfield` does not exist on `self`
/// 4) in the case where a `subfield` is private and only public subfields can be accessed
pub(crate) fn apply_subfields(
&self,
handler: &Handler,
engines: &Engines,
namespace: &Namespace,
subfields: &[Ident],
span: &Span,
) -> Result<ty::TyStructField, ErrorEmitted> {
@ -1263,24 +1269,44 @@ impl TypeInfo {
}
(TypeInfo::Struct(decl_ref), Some((first, rest))) => {
let decl = decl_engine.get_struct(decl_ref);
let field = match decl
.fields
.iter()
.find(|field| field.name.as_str() == first.as_str())
{
Some(field) => field.clone(),
let (struct_can_be_changed, is_public_struct_access) =
StructAccessInfo::get_info(&decl, namespace).into();
let field = match decl.find_field(first) {
Some(field) => {
if is_public_struct_access && field.is_private() {
// TODO: Uncomment this code and delete the one with warnings once struct field privacy becomes a hard error.
// https://github.com/FuelLabs/sway/issues/5520
// return Err(handler.emit_err(CompileError::StructFieldIsPrivate {
// field_name: first.into(),
// struct_name: decl.call_path.suffix.clone(),
// field_decl_span: field.name.span(),
// struct_can_be_changed,
// usage_context: StructFieldUsageContext::StructFieldAccess,
// }));
handler.emit_warn(CompileWarning {
span: first.span(),
warning_content: Warning::StructFieldIsPrivate {
field_name: first.into(),
struct_name: decl.call_path.suffix.clone(),
field_decl_span: field.name.span(),
struct_can_be_changed,
usage_context: StructFieldUsageContext::StructFieldAccess,
},
});
}
field.clone()
}
None => {
// gather available fields for the error message
let available_fields = decl
.fields
.iter()
.map(|x| x.name.as_str())
.collect::<Vec<_>>();
return Err(handler.emit_err(CompileError::FieldNotFound {
field_name: first.clone(),
return Err(handler.emit_err(CompileError::StructFieldDoesNotExist {
field_name: first.into(),
available_fields: decl.accessible_fields_names(is_public_struct_access),
is_public_struct_access,
struct_name: decl.call_path.suffix.clone(),
available_fields: available_fields.join(", "),
span: first.span(),
struct_decl_span: decl.span(),
struct_is_empty: decl.is_empty(),
usage_context: StructFieldUsageContext::StructFieldAccess,
}));
}
};
@ -1289,7 +1315,7 @@ impl TypeInfo {
} else {
type_engine
.get(field.type_argument.type_id)
.apply_subfields(handler, engines, rest, span)?
.apply_subfields(handler, engines, namespace, rest, span)?
};
Ok(field)
}
@ -1301,7 +1327,7 @@ impl TypeInfo {
_,
) => type_engine
.get(*type_id)
.apply_subfields(handler, engines, subfields, span),
.apply_subfields(handler, engines, namespace, subfields, span),
(TypeInfo::ErrorRecovery(err), _) => Err(*err),
// TODO-IG: Take a close look on this when implementing dereferencing.
(type_info, _) => Err(handler.emit_err(CompileError::FieldAccessOnNonStruct {

View file

@ -74,6 +74,18 @@ impl Diagnostic {
self.help.iter().filter(|help| !help.is_empty())
}
/// A help text that will never be displayed. Convenient when defining help lines
/// that are displayed only when a condition is met.
pub fn help_none() -> String {
String::new()
}
/// Displays an empty line in the help footer.
/// Convenient when defining visual separations within suggestions.
pub fn help_empty_line() -> String {
String::from(" ")
}
/// All the source files that are related to the diagnostic.
/// This means the source file of the issue itself as well
/// as source files of all the hints.
@ -82,8 +94,8 @@ impl Diagnostic {
let issue_is_in_source = self.issue.is_in_source();
// All source_path() unwrappings are safe because we check the existence
// of source in case of issue, and self.labels() returns
// All `source_path()` unwrappings are safe because we check the existence
// of source in case of an issue, and `self.labels()` returns
// only labels that are in source.
if issue_is_in_source && include_issue_source {
source_files.push(self.issue.source_path().unwrap());
@ -312,6 +324,14 @@ impl Hint {
label: Label::error(source_engine, span, text),
}
}
/// A [Hint] that will never be displayed. Convenient when defining [Hint]s that
/// are displayed only if a condition is met.
pub fn none() -> Self {
Self {
label: Label::default(),
}
}
}
impl std::ops::Deref for Hint {

View file

@ -1,14 +1,18 @@
use crate::convert_parse_tree_error::ConvertParseTreeError;
use crate::diagnostic::{Code, Diagnostic, Hint, Issue, Reason, ToDiagnostic};
use crate::formatting::*;
use crate::lex_error::LexError;
use crate::parser_error::ParseError;
use crate::type_error::TypeError;
use core::fmt;
use sway_types::constants::STORAGE_PURITY_ATTRIBUTE_NAME;
use sway_types::{BaseIdent, Ident, SourceEngine, SourceId, Span, Spanned};
use sway_types::style::to_snake_case;
use sway_types::{BaseIdent, Ident, IdentUnique, SourceEngine, Span, Spanned};
use thiserror::Error;
use self::StructFieldUsageContext::*;
#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
pub enum InterfaceName {
Abi(Ident),
@ -24,8 +28,16 @@ impl fmt::Display for InterfaceName {
}
}
// TODO: since moving to using Idents instead of strings, there are a lot of redundant spans in
// this type.
// TODO: Since moving to using Idents instead of strings, there are a lot of redundant spans in
// this type.
// Beware!!! If we remove those redundant spans (and we should!) we can have a situation that
// deduplication of error messages might remove errors that are actually not duplicates because
// although they point to the same Ident (in terms of name), the span can be different.
// Deduplication works on hashes and Ident's hash contains only the name and not the span.
// That's why we should consider always using IdentUnique whenever we extract the span from
// the provided Ident.
// Using IdentUnique will also clearly communicate that we are extracting the span from the
// provided identifier.
#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
pub enum CompileError {
#[error(
@ -206,17 +218,64 @@ pub enum CompileError {
it?"
)]
EnumNotFound { name: Ident, span: Span },
#[error("Initialization of struct \"{struct_name}\" is missing field \"{field_name}\".")]
StructMissingField {
/// This error is used only for error recovery and is not emitted as a compiler
/// error to the final compilation output. The compiler emits the cumulative error
/// [CompileError::StructInstantiationMissingFields] given below, and that one also
/// only if the struct can actually be instantiated.
#[error("Instantiation of the struct \"{struct_name}\" is missing field \"{field_name}\".")]
StructInstantiationMissingFieldForErrorRecovery {
field_name: Ident,
/// Original, non-aliased struct name.
struct_name: Ident,
span: Span,
},
#[error("Struct \"{struct_name}\" does not have field \"{field_name}\".")]
StructDoesNotHaveField {
field_name: Ident,
#[error("Instantiation of the struct \"{struct_name}\" is missing {} {}.",
if field_names.len() == 1 { "field" } else { "fields" },
field_names.iter().map(|name| format!("\"{name}\"")).collect::<Vec::<_>>().join(", "))]
StructInstantiationMissingFields {
field_names: Vec<Ident>,
/// Original, non-aliased struct name.
struct_name: Ident,
span: Span,
struct_decl_span: Span,
total_number_of_fields: usize,
},
#[error("Struct \"{struct_name}\" cannot be instantiated here because it has private fields.")]
StructCannotBeInstantiated {
/// Original, non-aliased struct name.
struct_name: Ident,
span: Span,
struct_decl_span: Span,
private_fields: Vec<Ident>,
/// All available public constructors if `is_in_storage_declaration` is false,
/// or only the public constructors that potentially evaluate to a constant
/// if `is_in_storage_declaration` is true.
constructors: Vec<String>,
/// True if the struct has only private fields.
all_fields_are_private: bool,
is_in_storage_declaration: bool,
struct_can_be_changed: bool,
},
#[error("Field \"{field_name}\" of the struct \"{struct_name}\" is private.")]
StructFieldIsPrivate {
field_name: IdentUnique,
/// Original, non-aliased struct name.
struct_name: Ident,
field_decl_span: Span,
struct_can_be_changed: bool,
usage_context: StructFieldUsageContext,
},
#[error("Field \"{field_name}\" does not exist in struct \"{struct_name}\".")]
StructFieldDoesNotExist {
field_name: IdentUnique,
/// Only public fields if `is_public_struct_access` is true.
available_fields: Vec<Ident>,
is_public_struct_access: bool,
/// Original, non-aliased struct name.
struct_name: Ident,
struct_decl_span: Span,
struct_is_empty: bool,
usage_context: StructFieldUsageContext,
},
#[error("No method named \"{method_name}\" found for type \"{type_name}\".")]
MethodNotFound {
@ -270,16 +329,6 @@ pub enum CompileError {
DeclIsNotAConstant { actually: String, span: Span },
#[error("This is a {actually}, not a type alias")]
DeclIsNotATypeAlias { actually: String, span: Span },
#[error(
"Field \"{field_name}\" not found on struct \"{struct_name}\". Available fields are:\n \
{available_fields}"
)]
FieldNotFound {
field_name: Ident,
available_fields: String,
struct_name: Ident,
span: Span,
},
#[error("Could not find symbol \"{name}\" in this scope.")]
SymbolNotFound { name: Ident, span: Span },
#[error("Symbol \"{name}\" is private.")]
@ -523,11 +572,29 @@ pub enum CompileError {
missing_patterns: String,
span: Span,
},
#[error("Pattern does not mention {}: {}",
if missing_fields.len() == 1 { "field" } else { "fields" },
missing_fields.join(", "))]
#[error("Struct pattern is missing the {}field{} {}.",
if *missing_fields_are_public { "public " } else { "" },
plural_s(missing_fields.len()),
sequence_to_str(missing_fields, Enclosing::DoubleQuote, 2)
)]
MatchStructPatternMissingFields {
missing_fields: Vec<String>,
missing_fields: Vec<Ident>,
missing_fields_are_public: bool,
/// Original, non-aliased struct name.
struct_name: Ident,
struct_decl_span: Span,
total_number_of_fields: usize,
span: Span,
},
#[error("Struct pattern must ignore inaccessible private field{} {}.",
plural_s(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2))]
MatchStructPatternMustIgnorePrivateFields {
private_fields: Vec<Ident>,
/// Original, non-aliased struct name.
struct_name: Ident,
struct_decl_span: Span,
all_fields_are_private: bool,
span: Span,
},
#[error("Variable \"{variable}\" is not defined in all alternatives.")]
@ -805,15 +872,17 @@ impl Spanned for CompileError {
DoesNotTakeTypeArgumentsAsPrefix { span, .. } => span.clone(),
TypeArgumentsNotAllowed { span } => span.clone(),
NeedsTypeArguments { span, .. } => span.clone(),
StructMissingField { span, .. } => span.clone(),
StructDoesNotHaveField { span, .. } => span.clone(),
StructInstantiationMissingFieldForErrorRecovery { span, .. } => span.clone(),
StructInstantiationMissingFields { span, .. } => span.clone(),
StructCannotBeInstantiated { span, .. } => span.clone(),
StructFieldIsPrivate { field_name, .. } => field_name.span(),
StructFieldDoesNotExist { field_name, .. } => field_name.span(),
MethodNotFound { span, .. } => span.clone(),
ModuleNotFound { span, .. } => span.clone(),
NotATuple { span, .. } => span.clone(),
NotAStruct { span, .. } => span.clone(),
NotIndexable { span, .. } => span.clone(),
FieldAccessOnNonStruct { span, .. } => span.clone(),
FieldNotFound { span, .. } => span.clone(),
SymbolNotFound { span, .. } => span.clone(),
ImportPrivateSymbol { span, .. } => span.clone(),
ImportPrivateModule { span, .. } => span.clone(),
@ -879,6 +948,7 @@ impl Spanned for CompileError {
GenericShadowsGeneric { name } => name.span(),
MatchExpressionNonExhaustive { span, .. } => span.clone(),
MatchStructPatternMissingFields { span, .. } => span.clone(),
MatchStructPatternMustIgnorePrivateFields { span, .. } => span.clone(),
MatchArmVariableNotDefinedInAllAlternatives { variable, .. } => variable.span(),
MatchArmVariableMismatchedType { variable, .. } => variable.span(),
NotAnEnum { span, .. } => span.clone(),
@ -952,6 +1022,10 @@ impl Spanned for CompileError {
}
}
// When implementing diagnostics, follow these two guidelines outlined in the Expressive Diagnostics RFC:
// - Guide-level explanation: https://github.com/FuelLabs/sway-rfcs/blob/master/rfcs/0011-expressive-diagnostics.md#guide-level-explanation
// - Wording guidelines: https://github.com/FuelLabs/sway-rfcs/blob/master/rfcs/0011-expressive-diagnostics.md#wording-guidelines
// For concrete examples, look at the existing diagnostics.
impl ToDiagnostic for CompileError {
fn to_diagnostic(&self, source_engine: &SourceEngine) -> Diagnostic {
let code = Code::semantic_analysis;
@ -1140,8 +1214,83 @@ impl ToDiagnostic for CompileError {
format!("Consider removing the variable \"{variable}\" altogether, or adding it to all alternatives."),
],
},
TraitNotImportedAtFunctionApplication { trait_name, function_name, function_call_site_span, trait_constraint_span, trait_candidates }=> {
// Make candidates order deterministic
MatchStructPatternMissingFields { missing_fields, missing_fields_are_public, struct_name, struct_decl_span, total_number_of_fields, span } => Diagnostic {
reason: Some(Reason::new(code(1), "Struct pattern has missing fields".to_string())),
issue: Issue::error(
source_engine,
span.clone(),
format!("Struct pattern is missing the {}field{} {}.",
if *missing_fields_are_public { "public " } else { "" },
plural_s(missing_fields.len()),
sequence_to_str(missing_fields, Enclosing::DoubleQuote, 2)
)
),
hints: vec![
Hint::help(
source_engine,
span.clone(),
"Struct pattern must either contain or ignore each struct field.".to_string()
),
Hint::info(
source_engine,
struct_decl_span.clone(),
format!("Struct \"{struct_name}\" is declared here, and has {} field{}.",
number_to_str(*total_number_of_fields),
plural_s(*total_number_of_fields),
)
),
],
help: vec![
// Consider ignoring the field "x_1" by using the `_` pattern: `x_1: _`.
// or
// Consider ignoring individual fields by using the `_` pattern. E.g, `x_1: _`.
format!("Consider ignoring {} field{} {}by using the `_` pattern{} `{}: _`.",
singular_plural(missing_fields.len(), "the", "individual"),
plural_s(missing_fields.len()),
singular_plural(missing_fields.len(), &format!("\"{}\" ", missing_fields[0]), ""),
singular_plural(missing_fields.len(), ":", ". E.g.,"),
missing_fields[0]
),
"Alternatively, consider ignoring all the missing fields by ending the struct pattern with `..`.".to_string(),
],
},
MatchStructPatternMustIgnorePrivateFields { private_fields, struct_name, struct_decl_span, all_fields_are_private, span } => Diagnostic {
reason: Some(Reason::new(code(1), "Struct pattern must ignore inaccessible private fields".to_string())),
issue: Issue::error(
source_engine,
span.clone(),
format!("Struct pattern must ignore inaccessible private field{} {}.",
plural_s(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
)
),
hints: vec![
Hint::help(
source_engine,
span.clone(),
format!("To ignore the private field{}, end the struct pattern with `..`.",
plural_s(private_fields.len()),
)
),
Hint::info(
source_engine,
struct_decl_span.clone(),
format!("Struct \"{struct_name}\" is declared here, and has {}.",
if *all_fields_are_private {
"all private fields".to_string()
} else {
format!("private field{} {}",
plural_s(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
)
}
)
),
],
help: vec![],
},
TraitNotImportedAtFunctionApplication { trait_name, function_name, function_call_site_span, trait_constraint_span, trait_candidates } => {
// Make candidates order deterministic.
let mut trait_candidates = trait_candidates.clone();
trait_candidates.sort();
let trait_candidates = &trait_candidates; // Remove mutability.
@ -1206,25 +1355,28 @@ impl ToDiagnostic for CompileError {
let mut help = vec![];
if trait_candidates.len() > 1 {
help.push(format!("There are these {} traits with the name \"{trait_name}\" available in the modules:", trait_candidates.len()));
help.push(format!("There are these {} traits with the name \"{trait_name}\" available in the modules:", number_to_str(trait_candidates.len())));
for trait_candidate in trait_candidates.iter() {
help.push(format!(" - {trait_candidate}"));
help.push(format!("{}- {trait_candidate}", Indent::Single));
}
help.push("To import the proper one follow these steps:".to_string());
help.push(format!(
" 1. Look at the definition of the \"{function_name}\"{}.",
"{}1. Look at the definition of the \"{function_name}\"{}.",
Indent::Single,
get_file_name(source_engine, trait_constraint_span.source_id())
.map_or("".to_string(), |file_name| format!(" in the \"{file_name}\""))
));
help.push(format!(
" 2. Detect which exact \"{trait_name}\" is used in the trait constraint in the \"{function_name}\"."
"{}2. Detect which exact \"{trait_name}\" is used in the trait constraint in the \"{function_name}\".",
Indent::Single
));
help.push(format!(
" 3. Import that \"{trait_name}\"{}.",
"{}3. Import that \"{trait_name}\"{}.",
Indent::Single,
get_file_name(source_engine, function_call_site_span.source_id())
.map_or("".to_string(), |file_name| format!(" into \"{file_name}\""))
));
help.push(format!(" E.g., assuming it is the first one on the list, use: `use {};`", trait_candidates[0]));
help.push(format!("{} E.g., assuming it is the first one on the list, use: `use {};`", Indent::Double, trait_candidates[0]));
}
help
@ -1253,6 +1405,314 @@ impl ToDiagnostic for CompileError {
],
help: vec![],
},
StructInstantiationMissingFields { field_names, struct_name, span, struct_decl_span, total_number_of_fields } => Diagnostic {
reason: Some(Reason::new(code(1), "Struct instantiation has missing fields".to_string())),
issue: Issue::error(
source_engine,
span.clone(),
format!("Instantiation of the struct \"{struct_name}\" is missing the field{} {}.",
plural_s(field_names.len()),
sequence_to_str(field_names, Enclosing::DoubleQuote, 2)
)
),
hints: vec![
Hint::help(
source_engine,
span.clone(),
"Struct instantiation must initialize all the fields of the struct.".to_string()
),
Hint::info(
source_engine,
struct_decl_span.clone(),
format!("Struct \"{struct_name}\" is declared here, and has {} field{}.",
number_to_str(*total_number_of_fields),
plural_s(*total_number_of_fields),
)
),
],
help: vec![],
},
StructCannotBeInstantiated { struct_name, span, struct_decl_span, private_fields, constructors, all_fields_are_private, is_in_storage_declaration, struct_can_be_changed } => Diagnostic {
reason: Some(Reason::new(code(1), "Struct cannot be instantiated due to inaccessible private fields".to_string())),
issue: Issue::error(
source_engine,
span.clone(),
format!("\"{struct_name}\" cannot be {}instantiated in this {}, due to {}inaccessible private field{}.",
if *is_in_storage_declaration { "" } else { "directly " },
if *is_in_storage_declaration { "storage declaration" } else { "module" },
singular_plural(private_fields.len(), "an ", ""),
plural_s(private_fields.len())
)
),
hints: vec![
Hint::help(
source_engine,
span.clone(),
format!("Inaccessible field{} {} {}.",
plural_s(private_fields.len()),
is_are(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 5)
)
),
Hint::help(
source_engine,
span.clone(),
if *is_in_storage_declaration {
"Structs with private fields can be instantiated in storage declarations only if they are declared in the same module as the storage.".to_string()
} else {
"Structs with private fields can be instantiated only within the module in which they are declared.".to_string()
}
),
if *is_in_storage_declaration {
Hint::help(
source_engine,
span.clone(),
"They can still be initialized in storage declarations if they have public constructors that evaluate to a constant.".to_string()
)
} else {
Hint::none()
},
if *is_in_storage_declaration {
Hint::help(
source_engine,
span.clone(),
"They can always be stored in storage by using the `read` and `write` functions provided in the `std::storage::storage_api`.".to_string()
)
} else {
Hint::none()
},
if !*is_in_storage_declaration && !constructors.is_empty() {
Hint::help(
source_engine,
span.clone(),
format!("\"{struct_name}\" can be instantiated via public constructors suggested below.")
)
} else {
Hint::none()
},
Hint::info(
source_engine,
struct_decl_span.clone(),
format!("Struct \"{struct_name}\" is declared here, and has {}.",
if *all_fields_are_private {
"all private fields".to_string()
} else {
format!("private field{} {}",
plural_s(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
)
}
)
),
],
help: {
let mut help = vec![];
if *is_in_storage_declaration {
help.push(format!("Consider initializing \"{struct_name}\" by finding an available constructor that evaluates to a constant{}.",
if *struct_can_be_changed {
", or implement a new one"
} else {
""
}
));
if !constructors.is_empty() {
help.push("Check these already available constructors. They might evaluate to a constant:".to_string());
// We always expect a very few candidates here. So let's list all of them by using `usize::MAX`.
for constructor in sequence_to_list(constructors, Indent::Single, usize::MAX) {
help.push(constructor);
}
};
help.push(Diagnostic::help_empty_line());
help.push(format!("Or you can always store instances of \"{struct_name}\" in the contract storage, by using the `std::storage::storage_api`:"));
help.push(format!("{}use std::storage::storage_api::{{read, write}};", Indent::Single));
help.push(format!("{}write(STORAGE_KEY, 0, my_{});", Indent::Single, to_snake_case(struct_name.as_str())));
help.push(format!("{}let my_{}_option = read::<{struct_name}>(STORAGE_KEY, 0);", Indent::Single, to_snake_case(struct_name.as_str())));
}
else if !constructors.is_empty() {
help.push(format!("Consider instantiating \"{struct_name}\" by using one of the available constructors{}:",
if *struct_can_be_changed {
", or implement a new one"
} else {
""
}
));
for constructor in sequence_to_list(constructors, Indent::Single, 5) {
help.push(constructor);
}
}
if *struct_can_be_changed {
if *is_in_storage_declaration || !constructors.is_empty() {
help.push(Diagnostic::help_empty_line());
}
if !*is_in_storage_declaration && constructors.is_empty() {
help.push(format!("Consider implementing a public constructor for \"{struct_name}\"."));
};
help.push(
// Alternatively, consider declaring the field "f" as public in "Struct": `pub f: ...,`.
// or
// Alternatively, consider declaring the fields "f" and "g" as public in "Struct": `pub <field>: ...,`.
// or
// Alternatively, consider declaring all fields as public in "Struct": `pub <field>: ...,`.
format!("Alternatively, consider declaring {} as public in \"{struct_name}\": `pub {}: ...,`.",
if *all_fields_are_private {
"all fields".to_string()
} else {
format!("{} {}",
singular_plural(private_fields.len(), "the field", "the fields"),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
)
},
if *all_fields_are_private {
"<field>".to_string()
} else {
match &private_fields[..] {
[field] => format!("{field}"),
_ => "<field>".to_string(),
}
},
)
)
};
help
}
},
StructFieldIsPrivate { field_name, struct_name, field_decl_span, struct_can_be_changed, usage_context } => Diagnostic {
reason: Some(Reason::new(code(1), "Private struct field is inaccessible".to_string())),
issue: Issue::error(
source_engine,
field_name.span(),
format!("Private field \"{field_name}\" {}is inaccessible in this module.",
match usage_context {
StructInstantiation { .. } | StorageDeclaration { .. } | PatternMatching { .. } => "".to_string(),
StorageAccess | StructFieldAccess => format!("of the struct \"{struct_name}\" "),
}
)
),
hints: vec![
Hint::help(
source_engine,
field_name.span(),
format!("Private fields can be {} only within the module in which their struct is declared.",
match usage_context {
StructInstantiation { .. } | StorageDeclaration { .. } => "initialized",
StorageAccess | StructFieldAccess => "accessed",
PatternMatching { .. } => "matched",
}
)
),
if matches!(usage_context, PatternMatching { has_rest_pattern } if !has_rest_pattern) {
Hint::help(
source_engine,
field_name.span(),
"Otherwise, they must be ignored by ending the struct pattern with `..`.".to_string()
)
} else {
Hint::none()
},
Hint::info(
source_engine,
field_decl_span.clone(),
format!("Field \"{field_name}\" {}is declared here as private.",
match usage_context {
StructInstantiation { .. } | StorageDeclaration { .. } | PatternMatching { .. } => format!("of the struct \"{struct_name}\" "),
StorageAccess | StructFieldAccess => "".to_string(),
}
)
),
],
help: vec![
if matches!(usage_context, PatternMatching { has_rest_pattern } if !has_rest_pattern) {
format!("Consider removing the field \"{field_name}\" from the struct pattern, and ending the pattern with `..`.")
} else {
Diagnostic::help_none()
},
if *struct_can_be_changed {
match usage_context {
StorageAccess | StructFieldAccess | PatternMatching { .. } => {
format!("{} declaring the field \"{field_name}\" as public in \"{struct_name}\": `pub {field_name}: ...,`.",
if matches!(usage_context, PatternMatching { has_rest_pattern } if !has_rest_pattern) {
"Alternatively, consider"
} else {
"Consider"
}
)
},
// For all other usages, detailed instructions are already given in specific messages.
_ => Diagnostic::help_none(),
}
} else {
Diagnostic::help_none()
},
],
},
StructFieldDoesNotExist { field_name, available_fields, is_public_struct_access, struct_name, struct_decl_span, struct_is_empty, usage_context } => Diagnostic {
reason: Some(Reason::new(code(1), "Struct field does not exist".to_string())),
issue: Issue::error(
source_engine,
field_name.span(),
format!("Field \"{field_name}\" does not exist in the struct \"{struct_name}\".")
),
hints: {
let public = if *is_public_struct_access { "public " } else { "" };
let (hint, show_struct_decl) = if *struct_is_empty {
(Some(format!("\"{struct_name}\" is an empty struct. It doesn't have any fields.")), false)
}
// If the struct anyhow cannot be instantiated (in the struct instantiation or storage declaration),
// we don't show any additional hints.
// Showing any available fields would be inconsistent and misleading, because they anyhow cannot be used.
// Besides, "Struct cannot be instantiated" error will provide all the explanations and suggestions.
else if (matches!(usage_context, StorageAccess) && *is_public_struct_access && available_fields.is_empty())
||
(matches!(usage_context, StructInstantiation { struct_can_be_instantiated: false } | StorageDeclaration { struct_can_be_instantiated: false })) {
// If the struct anyhow cannot be instantiated in the storage, don't show any additional hint
// if there is an attempt to access a non existing field of such non-instantiable struct.
// or
// Likewise, if we are in the struct instantiation or storage declaration and the struct
// cannot be instantiated.
(None, false)
} else if !available_fields.is_empty() {
// In all other cases, show the available fields.
const NUM_OF_FIELDS_TO_DISPLAY: usize = 4;
match &available_fields[..] {
[field] => (Some(format!("Only available {public}field is \"{field}\".")), false),
_ => (Some(format!("Available {public}fields are {}.", sequence_to_str(available_fields, Enclosing::DoubleQuote, NUM_OF_FIELDS_TO_DISPLAY))),
available_fields.len() > NUM_OF_FIELDS_TO_DISPLAY
),
}
}
else {
(None, false)
};
let mut hints = vec![];
if let Some(hint) = hint {
hints.push(Hint::help(source_engine, field_name.span(), hint));
};
if show_struct_decl {
hints.push(Hint::info(
source_engine,
struct_decl_span.clone(),
format!("Struct \"{struct_name}\" is declared here, and has {} {public}fields.",
number_to_str(available_fields.len())
)
));
}
hints
},
help: vec![],
},
_ => Diagnostic {
// TODO: Temporary we use self here to achieve backward compatibility.
// In general, self must not be used and will not be used once we
@ -1260,7 +1720,7 @@ impl ToDiagnostic for CompileError {
// of a diagnostic must come from the enum variant parameters.
issue: Issue::error(source_engine, self.span(), format!("{}", self)),
..Default::default()
}
}
}
}
}
@ -1289,12 +1749,15 @@ pub enum TypeNotAllowedReason {
StringSliceInConst,
}
/// Returns the file name (with extension) for the provided `source_id`,
/// or `None` if the `source_id` is `None` or the file name cannot be
/// obtained.
fn get_file_name(source_engine: &SourceEngine, source_id: Option<&SourceId>) -> Option<String> {
match source_id {
Some(source_id) => source_engine.get_file_name(source_id),
None => None,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum StructFieldUsageContext {
StructInstantiation { struct_can_be_instantiated: bool },
StorageDeclaration { struct_can_be_instantiated: bool },
StorageAccess,
PatternMatching { has_rest_pattern: bool },
StructFieldAccess,
// TODO: Distinguish between struct filed access and destructing
// once https://github.com/FuelLabs/sway/issues/5478 is implemented
// and provide specific suggestions for these two cases.
// (Destructing desugars to plain struct field access.)
}

View file

@ -0,0 +1,221 @@
//! This module contains various helper functions for easier formatting and creation of user-friendly
//! diagnostic messages.
use std::{
cmp,
fmt::{self, Display},
};
use sway_types::{SourceEngine, SourceId};
/// Returns the file name (with extension) for the provided `source_id`,
/// or `None` if the `source_id` is `None` or the file name cannot be
/// obtained.
pub(crate) fn get_file_name(
source_engine: &SourceEngine,
source_id: Option<&SourceId>,
) -> Option<String> {
match source_id {
Some(source_id) => source_engine.get_file_name(source_id),
None => None,
}
}
/// Returns reading-friendly textual representation for `number` smaller than or equal to 10
/// or its numeric representation if it is greater than 10.
pub(crate) fn number_to_str(number: usize) -> String {
match number {
0 => "zero".to_string(),
1 => "one".to_string(),
2 => "two".to_string(),
3 => "three".to_string(),
4 => "four".to_string(),
5 => "five".to_string(),
6 => "six".to_string(),
7 => "seven".to_string(),
8 => "eight".to_string(),
9 => "nine".to_string(),
10 => "ten".to_string(),
_ => format!("{number}"),
}
}
pub(crate) enum Enclosing {
#[allow(dead_code)]
None,
DoubleQuote,
}
impl Display for Enclosing {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::None => "",
Self::DoubleQuote => "\"",
},
)
}
}
pub(crate) enum Indent {
#[allow(dead_code)]
None,
Single,
Double,
}
impl Display for Indent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::None => "",
Self::Single => " ",
Self::Double => " ",
},
)
}
}
/// Returns reading-friendly textual representation of the `sequence`, with comma-separated
/// items and each item optionally enclosed in the specified `enclosing`.
/// If the sequence has more than `max_items` the remaining items are replaced
/// with the text "and <number> more".
///
/// E.g.:
/// [a] => "a"
/// [a, b] => "a" and "b"
/// [a, b, c] => "a", "b" and "c"
/// [a, b, c, d] => "a", "b", "c" and one more
/// [a, b, c, d, e] => "a", "b", "c" and two more
///
/// Panics if the `sequence` is empty, or `max_items` is zero.
pub(crate) fn sequence_to_str<T>(sequence: &[T], enclosing: Enclosing, max_items: usize) -> String
where
T: Display,
{
assert!(
!sequence.is_empty(),
"Sequence to display must not be empty."
);
assert!(
max_items > 0,
"Maximum number of items to display must be greater than zero."
);
let max_items = cmp::min(max_items, sequence.len());
let (to_display, remaining) = sequence.split_at(max_items);
let fmt_item = |item: &T| format!("{enclosing}{item}{enclosing}");
if !remaining.is_empty() {
format!(
"{}, and {} more",
to_display
.iter()
.map(fmt_item)
.collect::<Vec<_>>()
.join(", "),
number_to_str(remaining.len())
)
} else {
match to_display {
[] => unreachable!("There must be at least one item in the sequence."),
[item] => fmt_item(item),
[first_item, second_item] => {
format!("{} and {}", fmt_item(first_item), fmt_item(second_item))
}
_ => format!(
"{}, and {}",
to_display
.split_last()
.unwrap()
.1
.iter()
.map(fmt_item)
.collect::<Vec::<_>>()
.join(", "),
fmt_item(to_display.last().unwrap())
),
}
}
}
/// Returns reading-friendly textual representation of the `sequence`, with vertically
/// listed items and each item indented for the `indent` and preceded with the dash (-).
/// If the sequence has more than `max_items` the remaining items are replaced
/// with the text "and <number> more".
///
/// E.g.:
/// [a] =>
/// - a
/// [a, b] =>
/// - a
/// - b
/// [a, b, c, d, e] =>
/// - a
/// - b
/// - and three more
///
/// Panics if the `sequence` is empty, or `max_items` is zero.
pub(crate) fn sequence_to_list<T>(sequence: &[T], indent: Indent, max_items: usize) -> Vec<String>
where
T: Display,
{
assert!(
!sequence.is_empty(),
"Sequence to display must not be empty."
);
assert!(
max_items > 0,
"Maximum number of items to display must be greater than zero."
);
let mut result = vec![];
let max_items = cmp::min(max_items, sequence.len());
let (to_display, remaining) = sequence.split_at(max_items);
for item in to_display {
result.push(format!("{indent}- {item}"));
}
if !remaining.is_empty() {
result.push(format!(
"{indent}- and {} more",
number_to_str(remaining.len())
));
}
result
}
/// Returns "s" if `count` is different than 1, otherwise empty string.
/// Convenient for building simple plural of words.
pub(crate) fn plural_s(count: usize) -> &'static str {
if count == 1 {
""
} else {
"s"
}
}
/// Returns "is" if `count` is 1, otherwise "are".
pub(crate) fn is_are(count: usize) -> &'static str {
if count == 1 {
"is"
} else {
"are"
}
}
/// Returns `singular` if `count` is 1, otherwise `plural`.
pub(crate) fn singular_plural<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str {
if count == 1 {
singular
} else {
plural
}
}

View file

@ -1,6 +1,7 @@
pub mod convert_parse_tree_error;
pub mod diagnostic;
pub mod error;
pub mod formatting;
pub mod handler;
pub mod lex_error;
pub mod parser_error;

View file

@ -1,10 +1,15 @@
use crate::diagnostic::{Code, Diagnostic, Hint, Issue, Reason, ToDiagnostic};
use crate::error::StructFieldUsageContext; // TODO: Only temporary. Will be removed once struct field privacy becomes a hard error.
use crate::error::StructFieldUsageContext::*;
use crate::formatting::*;
use core::fmt;
use either::Either;
use sway_types::{Ident, SourceId, Span, Spanned};
use sway_types::{Ident, IdentUnique, SourceId, Span, Spanned};
// TODO: since moving to using Idents instead of strings,
// the warning_content will usually contain a duplicate of the span.
@ -121,6 +126,36 @@ pub enum Warning {
UsingDeprecated {
message: String,
},
// TODO: Remove all these warnings once private struct fields violations become a hard error.
// https://github.com/FuelLabs/sway/issues/5520
MatchStructPatternMustIgnorePrivateFields {
private_fields: Vec<Ident>,
/// Original, non-aliased struct name.
struct_name: Ident,
struct_decl_span: Span,
all_fields_are_private: bool,
span: Span,
},
StructFieldIsPrivate {
field_name: IdentUnique,
/// Original, non-aliased struct name.
struct_name: Ident,
field_decl_span: Span,
struct_can_be_changed: bool,
usage_context: StructFieldUsageContext,
},
StructCannotBeInstantiated {
/// Original, non-aliased struct name.
struct_name: Ident,
span: Span,
struct_decl_span: Span,
private_fields: Vec<Ident>,
constructors: Vec<String>,
/// True if the struct has only private fields.
all_fields_are_private: bool,
is_in_storage_declaration: bool,
struct_can_be_changed: bool,
},
}
impl fmt::Display for Warning {
@ -264,10 +299,25 @@ impl fmt::Display for Warning {
You can enable the new behavior with the --experimental-private-modules flag, which will become the default behavior in a later release.
More details are available in the related RFC: https://github.com/FuelLabs/sway-rfcs/blob/master/rfcs/0008-private-modules.md"),
UsingDeprecated { message } => write!(f, "{}", message),
MatchStructPatternMustIgnorePrivateFields { private_fields, .. } => write!(f,
"Struct pattern must ignore inaccessible private field{} {}.",
plural_s(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
),
StructFieldIsPrivate { field_name, struct_name, .. } => write!(f,
"Field \"{field_name}\" of the struct \"{struct_name}\" is private."
),
StructCannotBeInstantiated { struct_name, ..} => write!(f,
"Struct \"{struct_name}\" cannot be instantiated here because it has private fields."
),
}
}
}
#[allow(dead_code)]
const FUTURE_HARD_ERROR_HELP: &str =
"In future versions of Sway this warning will become a hard error.";
impl ToDiagnostic for CompileWarning {
fn to_diagnostic(&self, source_engine: &sway_types::SourceEngine) -> Diagnostic {
let code = Code::warnings;
@ -350,6 +400,277 @@ impl ToDiagnostic for CompileWarning {
]
}
},
MatchStructPatternMustIgnorePrivateFields { private_fields, struct_name, struct_decl_span, all_fields_are_private, span } => Diagnostic {
reason: Some(Reason::new(code(1), "Struct pattern must ignore inaccessible private fields".to_string())),
issue: Issue::warning(
source_engine,
span.clone(),
format!("Struct pattern must ignore inaccessible private field{} {}.",
plural_s(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
)
),
hints: vec![
Hint::help(
source_engine,
span.clone(),
format!("To ignore the private field{}, end the struct pattern with `..`.",
plural_s(private_fields.len()),
)
),
Hint::error(
source_engine,
span.clone(),
FUTURE_HARD_ERROR_HELP.to_string()
),
Hint::info(
source_engine,
struct_decl_span.clone(),
format!("Struct \"{struct_name}\" is declared here, and has {}.",
if *all_fields_are_private {
"all private fields".to_string()
} else {
format!("private field{} {}",
plural_s(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
)
}
)
),
],
help: vec![],
},
StructFieldIsPrivate { field_name, struct_name, field_decl_span, struct_can_be_changed, usage_context } => Diagnostic {
reason: Some(Reason::new(code(1), "Private struct field is inaccessible".to_string())),
issue: Issue::warning(
source_engine,
field_name.span(),
format!("Private field \"{field_name}\" {}is inaccessible in this module.",
match usage_context {
StructInstantiation { .. } | StorageDeclaration { .. } | PatternMatching { .. } => "".to_string(),
StorageAccess | StructFieldAccess => format!("of the struct \"{struct_name}\" "),
}
)
),
hints: vec![
Hint::help(
source_engine,
field_name.span(),
format!("Private fields can be {} only within the module in which their struct is declared.",
match usage_context {
StructInstantiation { .. } | StorageDeclaration { .. } => "initialized",
StorageAccess | StructFieldAccess => "accessed",
PatternMatching { .. } => "matched",
}
)
),
if matches!(usage_context, PatternMatching { has_rest_pattern } if !has_rest_pattern) {
Hint::help(
source_engine,
field_name.span(),
"Otherwise, they must be ignored by ending the struct pattern with `..`.".to_string()
)
} else {
Hint::none()
},
Hint::error(
source_engine,
field_name.span(),
FUTURE_HARD_ERROR_HELP.to_string()
),
Hint::info(
source_engine,
field_decl_span.clone(),
format!("Field \"{field_name}\" {}is declared here as private.",
match usage_context {
StructInstantiation { .. } | StorageDeclaration { .. } | PatternMatching { .. } => format!("of the struct \"{struct_name}\" "),
StorageAccess | StructFieldAccess => "".to_string(),
}
)
),
],
help: vec![
if matches!(usage_context, PatternMatching { has_rest_pattern } if !has_rest_pattern) {
format!("Consider removing the field \"{field_name}\" from the struct pattern, and ending the pattern with `..`.")
} else {
Diagnostic::help_none()
},
if *struct_can_be_changed {
match usage_context {
StorageAccess | StructFieldAccess | PatternMatching { .. } => {
format!("{} declaring the field \"{field_name}\" as public in \"{struct_name}\": `pub {field_name}: ...,`.",
if matches!(usage_context, PatternMatching { has_rest_pattern } if !has_rest_pattern) {
"Alternatively, consider"
} else {
"Consider"
}
)
},
// For all other usages, detailed instructions are already given in specific messages.
_ => Diagnostic::help_none(),
}
} else {
Diagnostic::help_none()
},
],
},
StructCannotBeInstantiated { struct_name, span, struct_decl_span, private_fields, constructors, all_fields_are_private, is_in_storage_declaration, struct_can_be_changed } => Diagnostic {
reason: Some(Reason::new(code(1), "Struct cannot be instantiated due to inaccessible private fields".to_string())),
issue: Issue::warning(
source_engine,
span.clone(),
format!("\"{struct_name}\" cannot be {}instantiated in this {}, due to {}inaccessible private field{}.",
if *is_in_storage_declaration { "" } else { "directly " },
if *is_in_storage_declaration { "storage declaration" } else { "module" },
singular_plural(private_fields.len(), "an ", ""),
plural_s(private_fields.len())
)
),
hints: vec![
Hint::help(
source_engine,
span.clone(),
format!("Inaccessible field{} {} {}.",
plural_s(private_fields.len()),
is_are(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 5)
)
),
Hint::help(
source_engine,
span.clone(),
if *is_in_storage_declaration {
"Structs with private fields can be instantiated in storage declarations only if they are declared in the same module as the storage.".to_string()
} else {
"Structs with private fields can be instantiated only within the module in which they are declared.".to_string()
}
),
if *is_in_storage_declaration {
Hint::help(
source_engine,
span.clone(),
"They can still be initialized in storage declarations if they have public constructors that evaluate to a constant.".to_string()
)
} else {
Hint::none()
},
if *is_in_storage_declaration {
Hint::help(
source_engine,
span.clone(),
"They can always be stored in storage by using the `read` and `write` functions provided in the `std::storage::storage_api`.".to_string()
)
} else {
Hint::none()
},
if !*is_in_storage_declaration && !constructors.is_empty() {
Hint::help(
source_engine,
span.clone(),
format!("\"{struct_name}\" can be instantiated via public constructors suggested below.")
)
} else {
Hint::none()
},
Hint::error(
source_engine,
span.clone(),
FUTURE_HARD_ERROR_HELP.to_string()
),
Hint::info(
source_engine,
struct_decl_span.clone(),
format!("Struct \"{struct_name}\" is declared here, and has {}.",
if *all_fields_are_private {
"all private fields".to_string()
} else {
format!("private field{} {}",
plural_s(private_fields.len()),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
)
}
)
),
],
help: {
let mut help = vec![];
if *is_in_storage_declaration {
help.push(format!("Consider initializing \"{struct_name}\" by finding an available constructor that evaluates to a constant{}.",
if *struct_can_be_changed {
", or implement a new one"
} else {
""
}
));
if !constructors.is_empty() {
help.push("Check these already available constructors. They might evaluate to a constant:".to_string());
// We always expect a very few candidates here. So let's list all of them by using `usize::MAX`.
for constructor in sequence_to_list(constructors, Indent::Single, usize::MAX) {
help.push(constructor);
}
};
help.push(Diagnostic::help_empty_line());
help.push(format!("Or you can always store instances of \"{struct_name}\" in the contract storage, by using the `std::storage::storage_api`:"));
help.push(format!("{}use std::storage::storage_api::{{read, write}};", Indent::Single));
help.push(format!("{}write(STORAGE_KEY, 0, my_{});", Indent::Single, to_snake_case(struct_name.as_str())));
help.push(format!("{}let my_{}_option = read::<{struct_name}>(STORAGE_KEY, 0);", Indent::Single, to_snake_case(struct_name.as_str())));
}
else if !constructors.is_empty() {
help.push(format!("Consider instantiating \"{struct_name}\" by using one of the available constructors{}:",
if *struct_can_be_changed {
", or implement a new one"
} else {
""
}
));
for constructor in sequence_to_list(constructors, Indent::Single, 5) {
help.push(constructor);
}
}
if *struct_can_be_changed {
if *is_in_storage_declaration || !constructors.is_empty() {
help.push(Diagnostic::help_empty_line());
}
if !*is_in_storage_declaration && constructors.is_empty() {
help.push(format!("Consider implementing a public constructor for \"{struct_name}\"."));
};
help.push(
// Alternatively, consider declaring the field "f" as public in "Struct": `pub f: ...,`.
// or
// Alternatively, consider declaring the fields "f" and "g" as public in "Struct": `pub <field>: ...,`.
// or
// Alternatively, consider declaring all fields as public in "Struct": `pub <field>: ...,`.
format!("Alternatively, consider declaring {} as public in \"{struct_name}\": `pub {}: ...,`.",
if *all_fields_are_private {
"all fields".to_string()
} else {
format!("{} {}",
singular_plural(private_fields.len(), "the field", "the fields"),
sequence_to_str(private_fields, Enclosing::DoubleQuote, 2)
)
},
if *all_fields_are_private {
"<field>".to_string()
} else {
match &private_fields[..] {
[field] => format!("{field}"),
_ => "<field>".to_string(),
}
},
)
)
};
help
}
},
_ => Diagnostic {
// TODO: Temporary we use self here to achieve backward compatibility.
// In general, self must not be used and will not be used once we

View file

@ -12,9 +12,9 @@ library;
/// represent different storage constructs.
pub struct StorageKey<T> {
/// The assigned location in storage.
slot: b256,
pub slot: b256,
/// The assigned offset based on the data structure `T`.
offset: u64,
pub offset: u64,
/// A unique identifier.
field_id: b256,
pub field_id: b256,
}

View file

@ -7,7 +7,7 @@ use ::hash::{Hash, Hasher};
/// The `Address` type, a struct wrapper around the inner `b256` value.
pub struct Address {
/// The underlying raw `b256` data of the address.
value: b256,
pub value: b256,
}
impl core::ops::Eq for Address {

View file

@ -16,7 +16,7 @@ use ::hash::{Hash, Hasher};
///
/// The SubId is used to differentiate between different assets that are created by the same contract.
pub struct AssetId {
value: b256,
pub value: b256,
}
impl Hash for AssetId {

View file

@ -9,7 +9,7 @@ use ::convert::From;
/// Guaranteed to be contiguous for use with ec-recover: `std::ecr::ec_recover`.
pub struct B512 {
/// The two `b256`s that make up the `B512`.
bytes: [b256; 2],
pub bytes: [b256; 2],
}
impl core::ops::Eq for B512 {

View file

@ -8,7 +8,7 @@ use ::option::Option::{self, *};
use ::convert::{From, Into, *};
struct RawBytes {
ptr: raw_ptr,
pub ptr: raw_ptr,
cap: u64,
}
@ -53,9 +53,9 @@ impl RawBytes {
/// A type used to represent raw bytes.
pub struct Bytes {
/// A barebones struct for the bytes.
buf: RawBytes,
pub buf: RawBytes,
/// The number of bytes being stored.
len: u64,
pub len: u64,
}
impl Bytes {

View file

@ -7,7 +7,7 @@ use ::hash::{Hash, Hasher};
/// The `ContractId` type, a struct wrapper around the inner `b256` value.
pub struct ContractId {
/// The underlying raw `b256` data of the contract id.
value: b256,
pub value: b256,
}
impl core::ops::Eq for ContractId {

View file

@ -410,7 +410,7 @@ where
///
/// fn foo() {
/// let result = sha256("Fuel");
/// assert(result = 0xa80f942f4112036dfc2da86daf6d2ef6ede3164dd56d1000eb82fa87c992450f);
/// assert(result == 0xa80f942f4112036dfc2da86daf6d2ef6ede3164dd56d1000eb82fa87c992450f);
/// }
/// ```
#[inline(never)]
@ -433,7 +433,7 @@ where
///
/// fn foo() {
/// let result = sha256_str_array(__to_str_array("Fuel"));
/// assert(result = 0xa80f942f4112036dfc2da86daf6d2ef6ede3164dd56d1000eb82fa87c992450f);
/// assert(result == 0xa80f942f4112036dfc2da86daf6d2ef6ede3164dd56d1000eb82fa87c992450f);
/// }
/// ```
#[inline(never)]
@ -469,7 +469,7 @@ pub fn sha256_str_array<S>(param: S) -> b256 {
///
/// fn foo() {
/// let result = keccak256("Fuel");
/// assert(result = 0x4375c8bcdc904e5f51752581202ae9ae2bb6eddf8de05d5567d9a6b0ae4789ad);
/// assert(result == 0x4375c8bcdc904e5f51752581202ae9ae2bb6eddf8de05d5567d9a6b0ae4789ad);
/// }
/// ```
#[inline(never)]

View file

@ -12,11 +12,11 @@ use ::vec::Vec;
/// A struct representing the call parameters of a function call.
pub struct CallParams {
/// Amount of the asset to transfer.
coins: u64,
pub coins: u64,
/// AssetId of the asset to transfer.
asset_id: AssetId,
pub asset_id: AssetId,
/// Gas to forward.
gas: u64,
pub gas: u64,
}
// TODO : Replace with `from` when implemented

View file

@ -15,7 +15,7 @@ use ::option::Option;
/// implemented, codepoints are *not* guaranteed to fall on byte boundaries
pub struct String {
/// The bytes representing the characters of the string.
bytes: Bytes,
pub bytes: Bytes,
}
impl String {

View file

@ -14,9 +14,9 @@ use ::result::Result::{self, *};
/// Represented as two 64-bit components: `(upper, lower)`, where `value = (upper << 64) + lower`.
pub struct U128 {
/// The most significant 64 bits of the `U128`.
upper: u64,
pub upper: u64,
/// The least significant 64 bits of the `U128`.
lower: u64,
pub lower: u64,
}
/// The error type used for `U128` type errors.

View file

@ -31,13 +31,13 @@ fn rsh_with_carry(word: u64, shift_amount: u64) -> (u64, u64) {
#[deprecated(note = "use the built-in type `u256` instead")]
pub struct U256 {
/// The most significant 64 bits of the `U256`.
a: u64,
pub a: u64,
/// The 65-128th most significant bits of the `U256`.
b: u64,
pub b: u64,
/// The 129-192nd most significant bits of the `U256`.
c: u64,
pub c: u64,
/// The 193-256th most significant bits of the `U256`.
d: u64,
pub d: u64,
}
/// The error type used for `U256` type errors.

View file

@ -7,7 +7,7 @@ use ::option::Option::{self, *};
use ::convert::From;
struct RawVec<T> {
ptr: raw_ptr,
pub ptr: raw_ptr,
cap: u64,
}
@ -130,7 +130,7 @@ impl<T> RawVec<T> {
/// A contiguous growable array type, written as `Vec<T>`, short for 'vector'.
pub struct Vec<T> {
buf: RawVec<T>,
pub buf: RawVec<T>,
len: u64,
}
@ -151,7 +151,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// // allocates when an element is pushed
/// vec.push(5);
/// }
@ -187,7 +187,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::with_capacity(2);
/// let mut vec = Vec::with_capacity(2);
/// // does not allocate
/// vec.push(5);
/// // does not re-allocate
@ -215,7 +215,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// vec.push(5);
/// let last_element = vec.pop().unwrap();
/// assert(last_element == 5);
@ -270,7 +270,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// vec.push(5);
/// vec.clear()
/// assert(vec.is_empty());
@ -296,7 +296,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// vec.push(5);
/// vec.push(10);
/// vec.push(15);
@ -332,7 +332,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// vec.push(5);
/// assert(vec.len() == 1);
/// vec.push(10);
@ -355,7 +355,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// assert(vec.is_empty());
/// vec.push(5);
/// assert(!vec.is_empty());
@ -386,7 +386,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// vec.push(5);
/// vec.push(10);
/// vec.push(15);
@ -440,7 +440,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// vec.push(5);
/// vec.push(10);
///
@ -491,7 +491,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
///
/// let res = vec.pop();
/// assert(res.is_none());
@ -527,7 +527,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// vec.push(5);
/// vec.push(10);
///
@ -570,7 +570,7 @@ impl<T> Vec<T> {
/// use std::vec::Vec;
///
/// fn foo() {
/// let vec = Vec::new();
/// let mut vec = Vec::new();
/// vec.push(5);
/// vec.push(10);
///

View file

@ -169,7 +169,7 @@ fn type_id_of_raw_ident(
curr_type_id = struct_decl
.fields
.iter()
.find(|field| field.name.to_string() == parts[i])
.find(|field| field.name.as_str() == parts[i])
.map(|field| field.type_argument.type_id);
}
i += 1;

View file

@ -485,6 +485,9 @@ impl Parse for UseTree {
impl Parse for TypeField {
fn parse(&self, ctx: &ParseContext) {
if let Some(visibility) = &self.visibility {
insert_keyword(ctx, visibility.span());
}
self.ty.parse(ctx);
}
}

View file

@ -94,7 +94,9 @@ impl Parse for ItemKind {
impl Parse for TypeField {
fn parse(parser: &mut Parser) -> ParseResult<TypeField> {
let visibility = parser.take();
Ok(TypeField {
visibility,
name: parser.parse()?,
colon_token: if parser.peek::<ColonToken>().is_some() {
parser.parse()

View file

@ -200,3 +200,15 @@ impl PartialOrd for IdentUnique {
}
impl Eq for IdentUnique {}
impl Spanned for IdentUnique {
fn span(&self) -> Span {
self.0.span()
}
}
impl fmt::Display for IdentUnique {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "{}", self.0.as_str())
}
}

View file

@ -69,9 +69,19 @@ impl Format for ItemStruct {
.map(|(type_field, comma_token)| (&type_field.value, comma_token))
.collect::<Vec<_>>();
// In first iteration we are going to be collecting the lengths of the struct variants.
// We need to include the `pub` keyword in the length, if the field is public,
// together with one space character between the `pub` and the name.
let variant_length: Vec<usize> = value_pairs
.iter()
.map(|(type_field, _)| type_field.name.as_str().len())
.map(|(type_field, _)| {
type_field
.visibility
.as_ref()
// We don't want to hard code the token here to `pub` or just hardcode 4.
// This is in case we introduce e.g. `pub(crate)` one day.
.map_or(0, |token| token.span().as_str().len() + 1)
+ type_field.name.as_str().len()
})
.collect();
// Find the maximum length in the variant_length vector that is still smaller than struct_field_align_threshold.
@ -87,6 +97,10 @@ impl Format for ItemStruct {
let value_pairs_iter = value_pairs.iter().enumerate();
for (var_index, (type_field, comma_token)) in value_pairs_iter.clone() {
write!(formatted_code, "{}", formatter.indent_to_str()?)?;
// If there is a visibility token add it to the formatted_code with a ` ` after it.
if let Some(visibility) = &type_field.visibility {
write!(formatted_code, "{} ", visibility.span().as_str())?;
}
// Add name
type_field.name.format(formatted_code, formatter)?;
let current_variant_length = variant_length[var_index];

View file

@ -199,6 +199,10 @@ impl Format for TypeField {
formatted_code: &mut FormattedCode,
formatter: &mut Formatter,
) -> Result<(), FormatterError> {
// If there is a visibility token add it to the formatted_code with a ` ` after it.
if let Some(visibility) = &self.visibility {
write!(formatted_code, "{} ", visibility.span().as_str())?;
}
write!(
formatted_code,
"{}{} ",

View file

@ -177,7 +177,11 @@ impl LeafSpans for CommaToken {
impl LeafSpans for TypeField {
fn leaf_spans(&self) -> Vec<ByteSpan> {
let mut collected_spans = vec![ByteSpan::from(self.name.span())];
let mut collected_spans = Vec::new();
if let Some(visibility) = &self.visibility {
collected_spans.push(ByteSpan::from(visibility.span()));
}
collected_spans.push(ByteSpan::from(self.name.span()));
collected_spans.push(ByteSpan::from(self.colon_token.span()));
collected_spans.append(&mut self.ty.leaf_spans());
collected_spans

View file

@ -79,6 +79,69 @@ pub struct Foo<T, P> {
&mut formatter,
);
}
#[test]
fn struct_alignment_with_public_fields() {
let mut formatter = Formatter::default();
formatter.config.structures.field_alignment = FieldAlignment::AlignFields(40);
check_with_formatter(
r#"contract;
pub struct Foo<T, P> {
barbazfoo: u64,
pub baz : bool,
}
"#,
r#"contract;
pub struct Foo<T, P> {
barbazfoo : u64,
pub baz : bool,
}
"#,
&mut formatter,
);
check_with_formatter(
r#"contract;
pub struct Foo<T, P> {
pub barbazfoo: u64,
baz : bool,
}
"#,
r#"contract;
pub struct Foo<T, P> {
pub barbazfoo : u64,
baz : bool,
}
"#,
&mut formatter,
);
}
#[test]
fn struct_public_fields() {
let mut formatter = Formatter::default();
formatter.config.structures.field_alignment = FieldAlignment::Off;
check_with_formatter(
r#"contract;
pub struct Foo<T, P> {
pub barbaz: T,
foo: u64,
pub baz : bool,
}
"#,
r#"contract;
pub struct Foo<T, P> {
pub barbaz: T,
foo: u64,
pub baz: bool,
}
"#,
&mut formatter,
);
}
#[test]
fn struct_ending_comma() {
check(

View file

@ -20,6 +20,16 @@ abi MyContract {
impl MyContract for Contract {
fn test_function() -> bool {
let s = MyStruct { x: 0 };
poke(s.x);
let s = MyConstStruct { x: 0 };
poke(s.x);
poke(MY_CONST_STRUCT);
true
}
}
fn poke<T>(_x: T) {}

View file

@ -1,7 +1,7 @@
category = "fail"
# check: $()const MY_CONST_STRUCT: MyConstStruct = MyConstStruct {};
# check: $()Initialization of struct "MyConstStruct" is missing field "x".
# check: $()Instantiation of the struct "MyConstStruct" is missing the field "x".
# check: $()my_struct: MyStruct = MyStruct {},
# check: $()Initialization of struct "MyStruct" is missing field "x".
# check: $()Instantiation of the struct "MyStruct" is missing the field "x".

View file

@ -1,7 +1,7 @@
library;
pub struct MyStruct {
x: u64,
pub x: u64,
}
impl MyStruct {

View file

@ -1,4 +1,8 @@
category = "fail"
# check: Point { x } => { x },
# nextln: $()Pattern does not mention field: y
#check: $()Struct pattern has missing fields
#check: $()Struct "Point" is declared here, and has two fields.
#check: $()Point { x } => { x },
#nextln: $()Struct pattern is missing the field "y".
#check: $()Consider ignoring the field "y" by using the `_` pattern: `y: _`.
#nextln: $()Alternatively, consider ignoring all the missing fields by ending the struct pattern with `..`.

View file

@ -0,0 +1,3 @@
[[package]]
name = "match_expressions_struct_non_existing_fields"
source = "member"

View file

@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
name = "match_expressions_struct_non_existing_fields"
entry = "main.sw"
implicit-std = false

View file

@ -0,0 +1,18 @@
script;
struct Struct {
x: u64,
}
fn main() -> u64 {
let p = Struct {
x: 0,
};
match p {
Struct { x, nn_1 } => { x },
Struct { x, nn_1, nn_2 } => { x },
};
0
}

View file

@ -0,0 +1,16 @@
category = "fail"
#check: $()Struct field does not exist
#check: $()Struct { x, nn_1 } => { x },
#nextln: $()Field "nn_1" does not exist in the struct "Struct".
#nextln: $()Only available field is "x".
#check: $()Struct field does not exist
#check: $()Struct { x, nn_1, nn_2 } => { x },
#nextln: $()Field "nn_1" does not exist in the struct "Struct".
#nextln: $()Only available field is "x".
#check: $()Struct field does not exist
#check: $()Struct { x, nn_1, nn_2 } => { x },
#nextln: $()Field "nn_2" does not exist in the struct "Struct".
#nextln: $()Only available field is "x".

View file

@ -1,5 +1,5 @@
library;
pub struct ExampleStruct<T> {
a_field: T
pub a_field: T
}

View file

@ -5,15 +5,15 @@ pub enum Enum {
}
pub struct Struct {
x: u64,
pub x: u64,
}
pub struct PubStruct {
x: u64,
pub x: u64,
}
pub struct GenericStruct<T> {
x: T,
pub x: T,
}
pub const X: u64 = 0u64;

View file

@ -14,9 +14,10 @@ enum Enum {
B: (),
}
// TODO: Remove all the `pub`s from all the structs once https://github.com/FuelLabs/sway/issues/5500 is fixed.
struct Struct {
x: u64,
y: u64,
pub x: u64,
pub y: u64,
}
impl Struct {
@ -27,8 +28,8 @@ impl Struct {
}
pub struct PubStruct {
x: u64,
y: u64,
pub x: u64,
pub y: u64,
}
impl PubStruct {
@ -39,8 +40,8 @@ impl PubStruct {
}
struct GenericStruct<T> {
x: T,
y: u64,
pub x: T,
pub y: u64,
}
impl<T> GenericStruct<T> {

View file

@ -0,0 +1,13 @@
[[package]]
name = "core"
source = "path+from-root-95C477BB1546ECF8"
[[package]]
name = "std"
source = "path+from-root-95C477BB1546ECF8"
dependencies = ["core"]
[[package]]
name = "storage_field_access_on_non_struct"
source = "member"
dependencies = ["std"]

View file

@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "storage_field_access_on_non_struct"
[dependencies]
std = { path = "../../../../../../sway-lib-std" }

View file

@ -0,0 +1,80 @@
contract;
abi ReadStorage {
#[storage(read)]
fn read_storage();
}
struct Struct01 {
x: u8,
second: Struct02,
}
impl Struct01 {
fn use_me(self) {
poke(self.x);
poke(self.second);
}
}
struct Struct02 {
x: u32,
third: Struct03,
}
impl Struct02 {
fn use_me(self) {
poke(self.x);
poke(self.third);
}
}
struct Struct03 {
x: u64,
}
impl Struct03 {
fn use_me(self) {
poke(self.x);
}
}
storage {
b: bool = true,
s_01: Struct01 = Struct01 { x: 0, second: Struct02 { x: 0, third: Struct03 { x: 0 } } },
}
impl ReadStorage for Contract {
#[storage(read)]
fn read_storage() {
let _ = storage.not_in_storage.read();
let _ = storage.b.read();
let _ = storage.b.prev_not_a_struct.read();
let s_01 = storage.s_01.read();
let _ = storage.s_01.x.read();
let _ = storage.s_01.x.prev_not_a_struct.read();
let _ = storage.s_01.non_existing_field.read();
let s_02 = storage.s_01.second.read();
let _ = storage.s_01.second.x.read();
let _ = storage.s_01.second.x.prev_not_a_struct.read();
let _ = storage.s_01.second.non_existing_field.read();
let s_03 = storage.s_01.second.third.read();
let _ = storage.s_01.second.third.x.read();
let _ = storage.s_01.second.third.x.prev_not_a_struct.read();
let _ = storage.s_01.second.third.non_existing_field.read();
s_01.use_me();
s_02.use_me();
s_03.use_me();
}
}
fn poke<T>(_x: T) { }

View file

@ -0,0 +1,36 @@
category = "fail"
#check: $()error
#check: $()let _ = storage.not_in_storage.read();
#check: $()Storage field not_in_storage does not exist
#check: $()error
#check: $()let _ = storage.b.prev_not_a_struct.read();
#check: $()This is a bool, not a struct. Fields can only be accessed on structs.
#check: $()error
#check: $()let _ = storage.s_01.x.prev_not_a_struct.read();
#check: $()This is a u8, not a struct. Fields can only be accessed on structs.
#check: $()Struct field does not exist
#check: $()let _ = storage.s_01.non_existing_field.read();
#check: $()Field "non_existing_field" does not exist in the struct "Struct01".
#check: $()Available fields are "x" and "second".
#check: $()error
#check: $()let _ = storage.s_01.second.x.prev_not_a_struct.read();
#check: $()This is a u32, not a struct. Fields can only be accessed on structs.
#check: $()Struct field does not exist
#check: $()let _ = storage.s_01.second.non_existing_field.read();
#check: $()Field "non_existing_field" does not exist in the struct "Struct02".
#check: $()Available fields are "x" and "third".
#check: $()error
#check: $()let _ = storage.s_01.second.third.x.prev_not_a_struct.read();
#check: $()This is a u64, not a struct. Fields can only be accessed on structs.
#check: $()Struct field does not exist
#check: $()let _ = storage.s_01.second.third.non_existing_field.read();
#check: $()Field "non_existing_field" does not exist in the struct "Struct03".
#check: $()Only available field is "x".

View file

@ -0,0 +1,3 @@
[[package]]
name = "struct_field_privacy_deconstructing"
source = "member"

View file

@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
name = "struct_field_privacy_deconstructing"
entry = "main.sw"
implicit-std = false

View file

@ -0,0 +1,14 @@
library;
pub struct LibStruct {
pub x_1: u64,
pub x_2: u64,
y_1: u64,
y_2: u64,
}
impl LibStruct {
pub fn new() -> Self {
Self { x_1: 0, x_2: 0, y_1: 0, y_2: 0 }
}
}

View file

@ -0,0 +1,40 @@
script;
mod lib;
use lib::*;
struct MainStruct {
pub x_1: u64,
pub x_2: u64,
y_1: u64,
y_2: u64,
}
impl MainStruct {
pub fn new() -> Self {
Self { x_1: 0, x_2: 0, y_1: 0, y_2: 0 }
}
}
fn main() {
let ls = LibStruct::new();
let LibStruct { x_1, x_2 } = ls;
let LibStruct { x_1, x_2, y_1 } = ls;
let LibStruct { x_1 } = ls;
let LibStruct { y_1, y_2 } = ls;
let LibStruct { x_1, y_2, .. } = ls;
let LibStruct { .. } = ls;
let LibStruct { } = ls;
let ms = MainStruct::new();
let MainStruct { x_1, x_2 } = ms;
let MainStruct { x_1, x_2, y_1 } = ms;
let MainStruct { x_1 } = ms;
let MainStruct { y_1, y_2 } = ms;
let MainStruct { x_1, y_2, .. } = ms;
let MainStruct { .. } = ms;
let MainStruct { } = ms;
}

View file

@ -0,0 +1,35 @@
category = "disabled" # Until struct field privacy warnings do not become hard errors.
#check: $()Private struct field is inaccessible
#check: $()let LibStruct { x_1, x_2, y_1 } = ls;
#nextln: $()Private field "y_1" of the struct "LibStruct" is inaccessible in this module.
#nextln: $()Private fields can be accessed only within the module in which their struct is declared.
#check: $()y_1: u64,
#nextln: $()Field "y_1" is declared here as private.
#check: $()Consider declaring the field "y_1" as public in "LibStruct": `pub y_1: ...,`.
#check: $()Private struct field is inaccessible
#check: $()let LibStruct { y_1, y_2 } = ls;
#nextln: $()Private field "y_1" of the struct "LibStruct" is inaccessible in this module.
#nextln: $()Private fields can be accessed only within the module in which their struct is declared.
#check: $()y_1: u64,
#nextln: $()Field "y_1" is declared here as private.
#check: $()Consider declaring the field "y_1" as public in "LibStruct": `pub y_1: ...,`.
#check: $()Private struct field is inaccessible
#check: $()let LibStruct { y_1, y_2 } = ls;
#nextln: $()Private field "y_2" of the struct "LibStruct" is inaccessible in this module.
#nextln: $()Private fields can be accessed only within the module in which their struct is declared.
#check: $()y_2: u64,
#nextln: $()Field "y_2" is declared here as private.
#check: $()Consider declaring the field "y_2" as public in "LibStruct": `pub y_2: ...,`.
#check: $()Private struct field is inaccessible
#check: $()let LibStruct { x_1, y_2, .. } = ls;
#nextln: $()Private field "y_2" of the struct "LibStruct" is inaccessible in this module.
#nextln: $()Private fields can be accessed only within the module in which their struct is declared.
#check: $()y_2: u64,
#nextln: $()Field "y_2" is declared here as private.
#check: $()Consider declaring the field "y_2" as public in "LibStruct": `pub y_2: ...,`.
#not: $()MainStruct

View file

@ -0,0 +1,3 @@
[[package]]
name = "struct_field_privacy_field_access"
source = "member"

View file

@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
name = "struct_field_privacy_field_access"
entry = "main.sw"
implicit-std = false

View file

@ -0,0 +1,24 @@
library;
pub struct LibStruct {
pub x: u64,
y: u64,
pub other: LibOtherStruct,
}
impl LibStruct {
pub fn new() -> Self {
Self { x: 0, y: 0, other: LibOtherStruct { x: 0, y: 0 } }
}
}
pub struct LibOtherStruct {
pub x: u64,
y: u64,
}
impl LibOtherStruct {
pub fn new() -> Self {
Self { x: 0, y: 0 }
}
}

View file

@ -0,0 +1,32 @@
script;
mod lib;
use lib::*;
struct MainStruct {
pub x: u64,
y: u64,
other: LibOtherStruct,
}
fn main() {
let ls = LibStruct::new();
let _ = ls.x;
let _ = ls.y;
let _ = ls.other.x;
let _ = ls.other.y;
let ms = MainStruct { x: 0, y: 0, other: LibOtherStruct::new() };
let _ = ms.x;
let _ = ms.y;
let _ = ms.other;
let _ = ms.other.x;
let _ = ms.other.y;
}

View file

@ -0,0 +1,27 @@
category = "disabled" # Until struct field privacy warnings do not become hard errors.
#check: $()Private struct field is inaccessible
#not: $()let _ = ls.x;
#check: $()let _ = ls.y;
#nextln: $()Private field "y" of the struct "LibStruct" is inaccessible in this module.
#check: $()y: u64,
#nextln: $()Field "y" is declared here as private.
#check: $()Consider declaring the field "y" as public in "LibStruct": `pub y: ...,`.
#check: $()Private struct field is inaccessible
#not: $()let _ = ls.other.x;
#check: $()let _ = ls.other.y;
#nextln: $()Private field "y" of the struct "LibOtherStruct" is inaccessible in this module.
#check: $()y: u64,
#nextln: $()Field "y" is declared here as private.
#check: $()Consider declaring the field "y" as public in "LibOtherStruct": `pub y: ...,`.
#not: Private field "y" of the struct "MainStruct" is inaccessible in this module.
#check: $()Private struct field is inaccessible
#not: $()let _ = ms.other.x;
#check: $()let _ = ms.other.y;
#nextln: $()Private field "y" of the struct "LibOtherStruct" is inaccessible in this module.
#check: $()y: u64,
#nextln: $()Field "y" is declared here as private.
#check: $()Consider declaring the field "y" as public in "LibOtherStruct": `pub y: ...,`.

Some files were not shown because too many files have changed in this diff Show more