Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Enrich document with using getter function over state struct instance #15526

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

haoyang9804
Copy link
Contributor

@haoyang9804 haoyang9804 commented Oct 18, 2024

Inspired by this issue: #15525. I guess it's better to add a document for this special return type since it's truly confusing.

Copy link

Thank you for your contribution to the Solidity compiler! A team member will follow up shortly.

If you haven't read our contributing guidelines and our review checklist before, please do it now, this makes the reviewing process and accepting your contribution smoother.

If you have any questions or need our help, feel free to post them in the PR or talk to us directly on the #solidity-dev channel on Matrix.

@cameel cameel requested review from nikola-matic, matheusaaguiar and r0qs and removed request for matheusaaguiar October 18, 2024 22:32
Copy link
Member

@cameel cameel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note on general structure here: the section on getters is already a bit too verbose IMO, with a lot of space and examples given to what are essentially obscure corner cases. I think we shouldn't make it even longer. We should consolidate the examples and make text more coherent. The focus should be kept primarily on explaining what getters are, the other things are just notes.

@haoyang9804
Copy link
Contributor Author

Just a note on general structure here: the section on getters is already a bit too verbose IMO, with a lot of space and examples given to what are essentially obscure corner cases. I think we shouldn't make it even longer. We should consolidate the examples and make text more coherent. The focus should be kept primarily on explaining what getters are, the other things are just notes.

How about merging struct example and mapping example? They are all corner cases that can lead to confusion.

@cameel
Copy link
Member

cameel commented Oct 21, 2024

Yes, they should be consolidated IMO. We could have just one example showing them all. Or even just a general description of how the ABI for a getter looks like. Which is relevant e.g. when you want to override a function with a getter and should be mentioned too.

@haoyang9804
Copy link
Contributor Author

Yes, they should be consolidated IMO. We could have just one example showing them all. Or even just a general description of how the ABI for a getter looks like. Which is relevant e.g. when you want to override a function with a getter and should be mentioned too.

Good suggestion. But I can only help with the consolidation part. I'm not very familiar with ABI.

@haoyang9804
Copy link
Contributor Author

I think the Complex example is useful for benefit users' understanding in getter function. Maybe we can remove this example and only leave examples of array and struct the mitigate confusion.

@haoyang9804
Copy link
Contributor Author

I think the Complex example is useful for benefit users' understanding in getter function. Maybe we can remove this example and only leave examples of array and struct the mitigate confusion.

I have merged the struct example and the array example. As for the Complex one that combine mapping and struct, since it implies no corner case, I removed it.
As for the ABI part, I think I can submit another pr after gaining some background.

@haoyang9804
Copy link
Contributor Author

@cameel Hi Kamil, any suggestions on the new commit?

Copy link
Collaborator

@matheusaaguiar matheusaaguiar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with the removal of the "complex example". It illustrates a case which we have to include, and which without mentioning it, the proposed added text of this PR would be incomplete.

The getter function of a struct returns a tuple whose elements are each individual struct member. But, as the complex example illustrates, in some cases they are omitted.
A simpler example would be the following:

contract C {
    struct S {
        int[2] a;
        uint256 x;
    }
S s;
}

In this case, a is omitted and the getter return type would be (uint256).

@haoyang9804
Copy link
Contributor Author

Thx for your review @matheusaaguiar, I have refined the doc, please take a look

Comment on lines 199 to 213
contract Complex {
struct Data {
// Struct Definition with multiple members
struct ComplexStruct {
uint a;
bytes3 b;
mapping(uint => uint) map;
uint[3] c;
uint[] d;
bytes e;
bool b;
int[2] arr;
}
mapping(uint => mapping(bool => Data[])) public data;
}

It generates a function of the following form. The mapping and arrays (with the
exception of byte arrays) in the struct are omitted because there is no good way
to select individual struct members or provide a key for the mapping:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should stay.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have merged it with the ComplexStruct

rather than the struct as a single object in memory.
The members returned appear in the order they are declared in the struct, provided they
are not omitted. Array-type members within the struct are excluded from the returned tuple.
Additionally, if the struct consists solely of array-type members, no getter function will be generated
Copy link
Collaborator

@matheusaaguiar matheusaaguiar Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only arrays but also mappings don't have a getter generated (in this case) because as explained in the "Complex Example":
there is no good way to select individual struct members or provide a key for the mapping

Also, I guess that in the above quote, it was meant to be array members instead of struct members originally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have changed it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I guess that in the above quote, it was meant to be array members instead of struct members originally.

Can someone else check and see if they interpret it the same way I did? @r0qs @nikola-matic @clonker

@haoyang9804
Copy link
Contributor Author

Hi @matheusaaguiar , is it OK to merge?

matheusaaguiar
matheusaaguiar previously approved these changes Nov 29, 2024
Copy link
Collaborator

@matheusaaguiar matheusaaguiar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @haoyang9804, sorry for the late response. The team was busy during this month attending devcon.
I think this is a good state for this section now.
@mehtavishwa30 please could you give a final look and also confirm my suggestion in #15526 (comment) ?


The next example is more complex:
When you declare a public state variable of a struct type, the generated getter function
returns specific member of the struct as separate elements within a tuple,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
returns specific member of the struct as separate elements within a tuple,
returns each member of the struct as separate elements within a tuple,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

b = data[arg1][arg2][arg3].b;
e = data[arg1][arg2][arg3].e;
}
// Getter function generated by the compiler
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to keep the explanation from before (with the correction i suggested in #15526 (comment)):

The mapping and arrays (with the exception of byte arrays)
in the struct are omitted because there is no good way to
select individual array members or provide a key for the mapping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have changed based on my understanding. plz check

Comment on lines +160 to +161
When considering arrays, mapping, and structs, the getter functions do not return
the entire state variable.
Copy link
Member

@cameel cameel Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally suggested to make things more concise here and merge the examples into one. I think things were going in that direction, but looking at the PR now, it seems that we went full circle and still ended up with multiple examples again :)

So a new suggestion: let's put everything starting from this place into a new subsection called Getters for Complex Types. That will at least separate these details from the main description of getters.

Then I'd expand this bit into an intro paragraph, along these lines:

Suggested change
When considering arrays, mapping, and structs, the getter functions do not return
the entire state variable.
Returning the entire data structure from a getter function in one go can be very expensive and in case of mappings outright impossible.
For this reason the return type of a getter will sometimes differ from the type of the state variable.
The general rule is that getters for arrays and mappings will return only one element at a time and certain fields can be omitted from structs.
This section will explain those rules in more detail.
Note that this mechanism is not meant to prevent the contract from returning the whole value, but simply to make
the default way to do this more aligned with best practices.
There are situations where returning the original type is perfectly acceptable and it can be easily achieved
by manually defining an external function that returns it.

Comment on lines +209 to +211
When you declare a public state variable of a struct type, the generated getter function
returns each member of the struct as separate elements within a tuple,
rather than the struct as a single object in memory.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When you declare a public state variable of a struct type, the generated getter function
returns each member of the struct as separate elements within a tuple,
rather than the struct as a single object in memory.
When you declare a public state variable of a struct type, the generated getter function
returns members of the struct as separate elements within a tuple,
rather than the struct as a single object.

are not omitted. The mapping and arrays (with the exception of byte arrays)
in the struct are omitted because there is no good way to
select individual array members or provide a key for the mapping
Additionally, if all struct members are omitted, no getter function will be generated.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should clarify that this is a compilation error. The compiler won't just silently skip the getter.

b = data[arg1][arg2][arg3].b;
e = data[arg1][arg2][arg3].e;
}
// Getter function generated by the compiler
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Getter function generated by the compiler
// Getter functions generated by the compiler

Comment on lines +230 to -208
// Struct Definition with multiple members
struct ComplexStruct {
uint a;
bytes3 b;
mapping(uint => uint) map;
uint[3] c;
uint[] d;
bytes e;
}
mapping(uint => mapping(bool => Data[])) public data;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the suggestions above are applied, I'd be fine with merging it, since it's already an improvement over what we had. But I also think the structure could be better and there a lot more details we could add.

As for the structure, I'd rather incorporate the relevant parts of the complex example into the respective types. For example we should show multi-dimensional arrays already in the initial array example.

As for missing details, I played with this a bit and I think this is a more complete list of corner cases here:

  1. Internal types (e.g. internal function pointers)
    • Cannot be the return type (compilation error).
  2. Arrays
    • n-dimensional array getters have n parameters.
    • bytes and string getters return the whole thing.
    • Elements cannot be of internal or recursive types (compilation error).
  3. Mappings
    • Elements cannot be of internal or recursive types (compilation error).
  4. Structs
    • Top-level struct gets unpacked into a tuple.
    • Fields of mapping types are omitted from the top-level struct.
      • But not omitted from nested structs (compilation error).
    • Fields of array types are omitted from the top-level struct.
      • But not omitted from nested structs (no error)
    • Fields of internal types are not omitted but cannot be returned (compilation error).
      • Unless they are elements of an array/mapping that would be omitted. Then they are omitted along with their container.
    • Fields of recursive types (e.g. a dynamic array or mapping with elements of that same struct type) are not omitted but cannot be returned (compilation error).
      • Unless they are elements of an array/mapping that would be omitted. TThen they are omitted along with their container.

Note that an array/mapping/struct containing an internal/recursive type is itself considered internal/recursive.

So my proposal would be to have one example for each of these four categories, showing both a simple case and the corner cases.

In any case, this PR has been stalled long enough so I won't block it if the above is not done, but that's basically how this section should look like in my opinion.

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants