Skip to content

Refactor TypeChecker visitor for MemberAccess expression.#16448

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

Refactor TypeChecker visitor for MemberAccess expression.#16448
rodiazet wants to merge 9 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.

expressionObjectType,
memberName,
_memberAccess.location(),
(*arguments).numArguments() == 0,
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

Potential null pointer dereference: arguments is dereferenced without checking if it's set. The arguments field is of type std::optional<FuncCallArguments> and is only set when the MemberAccess is used in a function call context. When accessing a member function without calling it (e.g., array.push instead of array.push()), arguments will not be set, causing undefined behavior. This is a pre-existing issue that was carried over during refactoring. Consider guarding this with arguments && (*arguments).numArguments() == 0 or passing arguments ? (*arguments).numArguments() == 0 : true to provide a safe default.

Suggested change
(*arguments).numArguments() == 0,
arguments ? (*arguments).numArguments() == 0 : true,

Copilot uses AI. Check for mistakes.
Comment on lines +3291 to +3296
isAccessedMemberLValue = !reinterpret_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData);
break;
case Type::Category::Function:
{
if (
auto const* expressionObjectFunctionType = reinterpret_cast<FunctionType const*>(expressionObjectType);
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This refactoring changes the cast from dynamic_cast (used in the original code and throughout the codebase) to reinterpret_cast, which is unsafe and deviates from established codebase conventions. The original code would have used dynamic_cast<StructType const*> with a null check. While the switch statement provides some guarantee about the category, reinterpret_cast bypasses all type safety and could lead to undefined behavior if the type hierarchy or category logic changes. This contradicts the PR description stating "Intentionally no logic has changed". Please revert to using dynamic_cast to maintain consistency with the rest of the codebase (see usage at lines 2742, 4308).

Suggested change
isAccessedMemberLValue = !reinterpret_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData);
break;
case Type::Category::Function:
{
if (
auto const* expressionObjectFunctionType = reinterpret_cast<FunctionType const*>(expressionObjectType);
{
if (auto const* structType = dynamic_cast<StructType const*>(expressionObjectType))
isAccessedMemberLValue = !structType->dataStoredIn(DataLocation::CallData);
break;
}
case Type::Category::Function:
{
if (
auto const* expressionObjectFunctionType = dynamic_cast<FunctionType const*>(expressionObjectType);
expressionObjectFunctionType &&

Copilot uses AI. Check for mistakes.
Comment on lines +3291 to +3296
isAccessedMemberLValue = !reinterpret_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData);
break;
case Type::Category::Function:
{
if (
auto const* expressionObjectFunctionType = reinterpret_cast<FunctionType const*>(expressionObjectType);
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This refactoring changes the cast from dynamic_cast to reinterpret_cast, which is unsafe and deviates from established codebase conventions. Please revert to using dynamic_cast as in the original code to maintain type safety and consistency.

Suggested change
isAccessedMemberLValue = !reinterpret_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData);
break;
case Type::Category::Function:
{
if (
auto const* expressionObjectFunctionType = reinterpret_cast<FunctionType const*>(expressionObjectType);
isAccessedMemberLValue = !dynamic_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData);
break;
case Type::Category::Function:
{
if (
auto const* expressionObjectFunctionType = dynamic_cast<FunctionType const*>(expressionObjectType);

Copilot uses AI. Check for mistakes.
if (tt->actualType()->category() == Type::Category::Array)
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
// although every subexpression is, so leaving this limited for now.
auto const* expressionObjectTypeType = reinterpret_cast<TypeType const*>(expressionObjectType);
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This refactoring changes the cast from dynamic_cast to reinterpret_cast, which is unsafe and deviates from established codebase conventions. Please revert to using dynamic_cast as in the original code to maintain type safety and consistency.

Suggested change
auto const* expressionObjectTypeType = reinterpret_cast<TypeType const*>(expressionObjectType);
auto const* expressionObjectTypeType = dynamic_cast<TypeType const*>(expressionObjectType);

Copilot uses AI. Check for mistakes.
}
case Type::Category::Magic:
{
auto const* expressionObjectMagicType = reinterpret_cast<MagicType const*>(expressionObjectType);
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This refactoring changes the cast from dynamic_cast to reinterpret_cast, which is unsafe and deviates from established codebase conventions. Please revert to using dynamic_cast as in the original code to maintain type safety and consistency.

Suggested change
auto const* expressionObjectMagicType = reinterpret_cast<MagicType const*>(expressionObjectType);
auto const* expressionObjectMagicType = dynamic_cast<MagicType const*>(expressionObjectType);

Copilot uses AI. Check for mistakes.
VirtualLookup requiredLookup = VirtualLookup::Static;

if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
if (auto accessedMemberFunctionTypeType = dynamic_cast<FunctionType const*>(accessedMemberAnnotation.type))
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

The variable name accessedMemberFunctionTypeType is confusing with "Type" appearing twice. Since this variable holds a pointer to the FunctionType of the accessed member, it should be named accessedMemberFunctionType for clarity. The double "Type" doesn't add meaningful information and makes the code harder to read.

Copilot uses AI. Check for mistakes.
@rodiazet rodiazet force-pushed the type-checker-member-access-refactoring branch from bf9c42e to 06bb313 Compare February 6, 2026 16:55
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.

2 participants