Conversation
…ontract types
`using SafeERC20 for IERC20` produced no completions on a variable declared as
`IERC20`. The suppression that caused it was deliberate but keyed on the wrong
thing:
let is_contract_name = resolved_node_id
.map(|nid| cache.contract_kinds.contains_key(&nid))
The intent, per the comment above it, was that typing a *type name* — `Lock.` —
should offer Lock's own members rather than functions from `using Pool for *`.
That is right. But the check asks "is this type a contract?", and `IERC20.` and
a `paymentToken` declared as `IERC20` carry the identical typeIdentifier, so the
answer is yes for both. Values lost their extensions along with type names.
The distinction cannot be recovered from the type. It has to come from where
the receiver was resolved, which already knows: `name_to_type` means a variable,
`name_to_node_id` means a contract/library/interface name. That was simply
discarded before reaching `completions_for_type`.
Threads a `ReceiverKind` down from the resolvers instead. Plain access keeps
whatever the name resolved to; a cast (`IERC20(addr).`), a mapping index, and
any member reached by walking a chain are values regardless of the name in
front, so they get extensions too.
One extra hop was needed for the cast case. A contract name with no reverse
entry in `type_to_node` resolves to the synthetic `__node_id_N` marker, which
can never match a real using-for key like `t_contract$_IHooks_$1840`, so
`lookup_using_for` now falls back to matching on the node id.
Covers repro 1 of asyncswap#225. The nested-enum and live-edit repros in that issue are
separate and not addressed here.
Tests use the poolmanager.json fixture, which carries `using Hooks for IHooks`
and a `hooks` variable declared as `IHooks`. `hasPermission` and
`isValidHookAddress` exist only on the library, never on the interface, so they
tell the two sources apart:
hooks. must offer them — failed on main
IHooks(addr). must offer them — failed on main
IHooks. must not — already correct, now locked in
rifuki
force-pushed
the
fix/completion-using-for-on-values
branch
from
August 25, 2026 09:59
e48c50f to
b4f3ea9
Compare
rifuki
marked this pull request as draft
August 25, 2026 10:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First slice of #225 — the
using SafeERC20 for IERC20case, where extensions never showed up on a variable declared asIERC20.The suppression behind it was deliberate. The comment explains it: typing
Lock.should give Lock's own members, not everything fromusing Pool for *. That's right. But the check asks whether the type is a contract, andIERC20.and apaymentTokendeclared asIERC20carry the sametypeIdentifier— so values got suppressed along with type names.That distinction can't come from the type. It has to come from where the receiver was resolved, which already knows: a
name_to_typehit is a variable, aname_to_node_idhit is a contract name. It was just being dropped beforecompletions_for_typesaw it. This threads aReceiverKinddown instead. Casts, mapping indexes and chained members count as values regardless of the name in front, soIERC20(addr).gets extensions too. Public signatures are unchanged.One extra hop for the cast case: a contract name with no reverse entry in
type_to_nodefalls back to the synthetic__node_id_Nmarker, which can never match a real key liket_contract$_IHooks_$1840, solookup_using_fornow also matches on node id.Tests use the
poolmanager.jsonfixture, which already hasusing Hooks for IHooksand ahooksvariable typedIHooks.hasPermissionandisValidHookAddressonly exist on the library, never on the interface, so they tell the two apart — I deliberately don't assert on names likebeforeSwap, sinceIHooksdeclares those itself.hooks.andIHooks(addr).both fail onmain;IHooks.not offering them already worked and is now locked in.cargo test662 passed, clippy and fmt clean.This is extracted and rebased out of my draft #226. The nested-enum and live-edit parts of #225 are separate and not here. #226 shows a stale diff that predates #224 — I'll split the rest out and close it.