Skip to content

Refactor TypeChecker visitor for MemberAccess expression.#16448

Open
rodiazet wants to merge 11 commits intodevelopfrom
type-checker-member-access-refactoring
Open

Refactor TypeChecker visitor for MemberAccess expression.#16448
rodiazet wants to merge 11 commits intodevelopfrom
type-checker-member-access-refactoring

Conversation

@rodiazet
Copy link
Contributor

@rodiazet rodiazet commented Feb 6, 2026

This PR refactors implementation of the TypeChecker visitor for MemberAccess expression. It is needed based on the outcome from the discussion on changes in #16376 (comment).

Intentionally no logic has changed.

@stackenbotten3000
Copy link

There was an error when running chk_coding_style for commit 63d0c0750dbcb9c4c8982bc4931defae244044ef:

Coding style error:
libsolidity/analysis/TypeChecker.cpp:26:#include "range/v3/view/any_view.hpp"

Please check that your changes are working as intended.

@rodiazet rodiazet force-pushed the type-checker-member-access-refactoring branch from cf38fdd to fe24189 Compare February 6, 2026 14:20
@rodiazet rodiazet force-pushed the type-checker-member-access-refactoring branch from 1131cb9 to bf9c42e Compare February 6, 2026 14:39
@rodiazet rodiazet changed the title Refactor TypeChecker visitor od MemberAccess expression. Refactor TypeChecker visitor for MemberAccess expression. Feb 6, 2026
@rodiazet rodiazet requested a review from Copilot February 6, 2026 14:52
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

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, and validateAccessMemberFunctionType
  • Reorganized the main visitor logic using exhaustive switch statements for better structure
  • Improved variable naming for clarity (e.g., exprTypeexpressionObjectType)

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:

  1. Critical: Null pointer dereference at line 3278 when dereferencing arguments without checking if it's set
  2. Moderate: Four instances of unsafe reinterpret_cast usage instead of the codebase's standard dynamic_cast pattern
  3. Nit: Confusing variable naming with double "Type" suffix

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@rodiazet rodiazet force-pushed the type-checker-member-access-refactoring branch 3 times, most recently from cc730b5 to 60443be Compare February 9, 2026 08:31
size_t const initialMemberCount = possibleMembers.size();
if (initialMemberCount > 1 && arguments)
// do overload resolution
for (auto it = _possibleMembers.begin(); it != _possibleMembers.end();)
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

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 plan to make it i a separated PR/PRs.

/// @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(
Copy link
Collaborator

Choose a reason for hiding this comment

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

"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.

Suggested change
void validateAccessMemberFunctionType(
void checkMemberFunctionAccess(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I think checkAccessedMemberFunction is even better.

Comment on lines +3272 to +3273
// Validate an accessed member function type.
validateAccessMemberFunctionType(
Copy link
Collaborator

@cameel cameel Feb 17, 2026

Choose a reason for hiding this comment

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

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.

Comment on lines +3274 to +3279
accessedMemberFunctionType,
expressionObjectType,
memberName,
_memberAccess.location(),
arguments && (*arguments).numArguments() > 0,
accessedMemberAnnotation.referencedDeclaration != nullptr
Copy link
Collaborator

Choose a reason for hiding this comment

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

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.

Comment on lines 3240 to 3245
handleUnresolvedMemberAccessErrors(
_memberAccess,
expressionObjectType,
memberName,
possibleMemberCountBeforeOverloading
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

And here I would reduce it to these parameters:

Suggested change
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.

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> {
Copy link
Collaborator

@cameel cameel Feb 17, 2026

Choose a reason for hiding this comment

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

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().

Comment on lines +3232 to +3256
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;
Copy link
Collaborator

Choose a reason for hiding this comment

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

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().

@rodiazet rodiazet force-pushed the type-checker-member-access-refactoring branch from 737b9e4 to c8b5654 Compare February 24, 2026 14:41
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())

Choose a reason for hiding this comment

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

Coding style error

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again \< does not match TAB \t on macos, but it matches TAB on GNU.

@rodiazet rodiazet force-pushed the type-checker-member-access-refactoring branch from bcb45e9 to 70b945e Compare February 24, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants