Refactor TypeChecker visitor for MemberAccess expression.#16448
Refactor TypeChecker visitor for MemberAccess expression.#16448
TypeChecker visitor for MemberAccess expression.#16448Conversation
|
There was an error when running Please check that your changes are working as intended. |
cf38fdd to
fe24189
Compare
1131cb9 to
bf9c42e
Compare
TypeChecker visitor od MemberAccess expression.TypeChecker visitor for MemberAccess expression.
There was a problem hiding this comment.
Pull request overview
This PR refactors the TypeChecker::visit(MemberAccess) implementation by extracting three helper methods and reorganizing the code into a more structured switch-case pattern. The changes aim to improve code maintainability and readability without altering the underlying logic.
Changes:
- Extracted three helper methods:
performOverloadedResolution,handleUnresolvedMemberAccessErrors, andvalidateAccessMemberFunctionType - Reorganized the main visitor logic using exhaustive switch statements for better structure
- Improved variable naming for clarity (e.g.,
exprType→expressionObjectType)
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| libsolidity/analysis/TypeChecker.h | Adds declarations for three new helper methods with detailed documentation |
| libsolidity/analysis/TypeChecker.cpp | Refactors MemberAccess visitor by extracting helpers and reorganizing with switch statements; removes unused includes |
However, I found several critical and moderate issues:
- Critical: Null pointer dereference at line 3278 when dereferencing
argumentswithout checking if it's set - Moderate: Four instances of unsafe
reinterpret_castusage instead of the codebase's standarddynamic_castpattern - Nit: Confusing variable naming with double "Type" suffix
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
cc730b5 to
60443be
Compare
libsolidity/analysis/TypeChecker.cpp
Outdated
| size_t const initialMemberCount = possibleMembers.size(); | ||
| if (initialMemberCount > 1 && arguments) | ||
| // do overload resolution | ||
| for (auto it = _possibleMembers.begin(); it != _possibleMembers.end();) |
There was a problem hiding this comment.
std::remove_if(_possibleMembers, [](auto const& member) { return ....; })?
Of course, you're not liable to change this since it was already like that, and would make the review a bit more difficult then just comparing the moved around code snippets.
There was a problem hiding this comment.
I plan to make it i a separated PR/PRs.
libsolidity/analysis/TypeChecker.h
Outdated
| /// @param _location The source location where the member function is accessed. | ||
| /// @param _hasArguments Indicates whether the member function is accessed without arguments. | ||
| /// @param _isDefined Specifies if the member function is fully defined or abstract. | ||
| void validateAccessMemberFunctionType( |
There was a problem hiding this comment.
"access" -> "accessed"
Also, we usually do not refer to the analysis checks as "validations", so "check" would be a more typical verb in this context.
Restricting it to just "type" part is also unnecessary. The function looks like a good place for any checks related to that member we might have here.
| void validateAccessMemberFunctionType( | |
| void checkMemberFunctionAccess( |
There was a problem hiding this comment.
Good point, I think checkAccessedMemberFunction is even better.
libsolidity/analysis/TypeChecker.cpp
Outdated
| // Validate an accessed member function type. | ||
| validateAccessMemberFunctionType( |
There was a problem hiding this comment.
I'd leave out comments when they are this obvious. It's all literally in the function name now :)
The comments should explain why we are doing it. I would only restate what we are doing if the code is complex enough and names bad enough that it's not obvious at a glance.
For example in:
// Lookup type required to find the function declaration. Set default to `static`.
VirtualLookup requiredLookup = VirtualLookup::Static;
the first part adds useful context, but the second is already obvious from the code.
libsolidity/analysis/TypeChecker.cpp
Outdated
| accessedMemberFunctionType, | ||
| expressionObjectType, | ||
| memberName, | ||
| _memberAccess.location(), | ||
| arguments && (*arguments).numArguments() > 0, | ||
| accessedMemberAnnotation.referencedDeclaration != nullptr |
There was a problem hiding this comment.
All of this information is trivially obtainable from _memberAccess. I'd pass in only that variable and let the function get other values from it on its own. It does repeat some info you already have available in locals here, but none of it requires a significant calculation. It will make it easier to understand the function in isolation.
libsolidity/analysis/TypeChecker.cpp
Outdated
| handleUnresolvedMemberAccessErrors( | ||
| _memberAccess, | ||
| expressionObjectType, | ||
| memberName, | ||
| possibleMemberCountBeforeOverloading | ||
| ); |
There was a problem hiding this comment.
And here I would reduce it to these parameters:
| handleUnresolvedMemberAccessErrors( | |
| _memberAccess, | |
| expressionObjectType, | |
| memberName, | |
| possibleMemberCountBeforeOverloading | |
| ); | |
| handleUnresolvedMemberAccessErrors(_memberAccess, possibleMembers); |
TBH I prefer not introducing locals for expressions that are already short and not expensive to compute. Finding good names for them is not always simple and you often end up with either a short one that's ambiguous/vague or one that's as long as the original expression. For example I'd much rather read type(_memberAccess.expression()) than expressionObjectType. The latter forces me to look up the definition to check what it is.
libsolidity/analysis/TypeChecker.cpp
Outdated
| auto [errorId, description] = [&]() -> std::tuple<ErrorId, std::string> { | ||
| std::string errorMsg = "Member \"" + memberName + "\" not found or not visible " | ||
| "after argument-dependent lookup in " + exprType->humanReadableName() + "."; | ||
| auto [errorId, description] = [&]() -> std::tuple<ErrorId, std::string> { |
There was a problem hiding this comment.
This is another thing that could be refactored here. Instead of this confusing pattern where you define a lambda and immediately call it, it would be much simpler to instead define a small helper lambda that gets the error code and description and runs m_errorReporter.fatalTypeError().
libsolidity/analysis/TypeChecker.cpp
Outdated
| MemberList::MemberMap possibleMembers = expressionObjectType->members(currentDefinitionScope()).membersByName(memberName); | ||
| size_t const possibleMemberCountBeforeOverloading = possibleMembers.size(); | ||
| if (possibleMemberCountBeforeOverloading > 1 && arguments) | ||
| performOverloadedResolution(expressionObjectType, possibleMembers, *arguments); | ||
|
|
||
| accessedMemberAnnotation.isConstant = false; | ||
|
|
||
| if (possibleMembers.empty()) | ||
| handleUnresolvedMemberAccessErrors( | ||
| _memberAccess, | ||
| expressionObjectType, | ||
| memberName, | ||
| possibleMemberCountBeforeOverloading | ||
| ); | ||
| } | ||
| else if (possibleMembers.size() > 1) | ||
| m_errorReporter.fatalTypeError( | ||
| 6675_error, | ||
| _memberAccess.location(), | ||
| "Member \"" + memberName + "\" not unique " | ||
| "after argument-dependent lookup in " + exprType->humanReadableName() + | ||
| "after argument-dependent lookup in " + expressionObjectType->humanReadableName() + | ||
| (memberName == "value" ? " - did you forget the \"payable\" modifier?" : ".") | ||
| ); | ||
|
|
||
| annotation.referencedDeclaration = possibleMembers.front().declaration; | ||
| annotation.type = possibleMembers.front().type; | ||
| accessedMemberAnnotation.referencedDeclaration = possibleMembers.front().declaration; | ||
| accessedMemberAnnotation.type = possibleMembers.front().type; |
There was a problem hiding this comment.
All of this is still a part of what I'd consider overload resolution. I'd combine it with what you have in handleUnresolvedMemberAccessErrors() into a single function that returns possibleMembers.front() if resolution is successful and reports a fatal error if it's not.
performOverloadedResolution() (or maybe resolveOverloads()) would actually be a good name for this function. The function that is now has this name would be better off as something more specific, like filterOutOverloadsNotMatchingArguments().
Remove unnecessary includes
737b9e4 to
c8b5654
Compare
libsolidity/analysis/TypeChecker.cpp
Outdated
| TypePointers const& t = funType->returnParameterTypes(); | ||
| // If no detailed error were reported, issues a general unresolved member error. | ||
| // TODO: Consider merging `storage` member case from the top of this function with the rest of the checks. | ||
| if(errorCount == m_errorReporter.errorCount()) |
There was a problem hiding this comment.
Again \< does not match TAB \t on macos, but it matches TAB on GNU.
c8b5654 to
bcb45e9
Compare
bcb45e9 to
70b945e
Compare
This PR refactors implementation of the
TypeCheckervisitor forMemberAccessexpression. It is needed based on the outcome from the discussion on changes in #16376 (comment).Intentionally no logic has changed.