Skip to main content

neo_devpack_solidity/solidity/
solidity_analyse.rs

1pub fn analyse_source(source: &str) -> Result<ContractMetadata, SolidityError> {
2    let mut contracts = analyse_all_sources(source)?;
3    Ok(contracts.swap_remove(0))
4}
5
6pub fn analyse_all_sources(source: &str) -> Result<Vec<ContractMetadata>, SolidityError> {
7    fn is_builtin_library_name(name: &str) -> bool {
8        matches!(
9            name,
10            "Runtime" | "abi" | "Storage" | "Syscalls" | "Neo" | "NativeCalls"
11        )
12    }
13
14    fn normalize_library_for_neo(mut contract: ContractIR) -> ContractIR {
15        if !matches!(contract.kind, ContractKind::Library) {
16            return contract;
17        }
18
19        // Neo N3 libraries are inlined into contracts; treat externally visible
20        // library functions as internal helper functions to avoid exposing them
21        // through the contract ABI.
22        for function in &mut contract.functions {
23            if !matches!(function.ty, FunctionTy::Function) {
24                continue;
25            }
26            if matches!(
27                function.visibility,
28                VisibilityKind::External | VisibilityKind::Public
29            ) {
30                function.visibility = VisibilityKind::Internal;
31            }
32        }
33
34        // Keep merged library state as internal implementation detail.
35        // Public library constants would otherwise synthesize contract-level
36        // getters and create ABI/name collisions in the consuming contract.
37        for state in &mut contract.state_variables {
38            state.visibility = Some("internal".to_string());
39        }
40
41        contract
42    }
43
44    fn collect_contract_types(
45        contract_map: &std::collections::HashMap<String, ContractIR>,
46    ) -> Vec<String> {
47        let mut contract_types: Vec<String> = Vec::new();
48        let mut seen_contract_types = std::collections::HashSet::new();
49
50        for contract in contract_map.values() {
51            let include_as_contract_type = match contract.kind {
52                ContractKind::Contract | ContractKind::AbstractContract | ContractKind::Interface => {
53                    true
54                }
55                ContractKind::Library => !is_builtin_library_name(contract.name.as_str()),
56            };
57
58            if include_as_contract_type
59                && seen_contract_types.insert(contract.name.to_ascii_lowercase())
60            {
61                contract_types.push(contract.name.clone());
62            }
63        }
64
65        contract_types
66    }
67
68    let mut primary = Vec::new();
69    let mut fallback = Vec::new();
70
71    let contracts = parse_source(source)?;
72    for contract in contracts {
73        if matches!(
74            contract.kind,
75            ContractKind::Contract | ContractKind::AbstractContract
76        ) {
77            primary.push(contract);
78        } else {
79            fallback.push(contract);
80        }
81    }
82
83    let has_primary = !primary.is_empty();
84    let pre_merge_contract_map: std::collections::HashMap<String, ContractIR> = primary
85        .iter()
86        .chain(fallback.iter())
87        .map(|contract| (contract.name.clone(), contract.clone()))
88        .collect();
89    let contract_types = collect_contract_types(&pre_merge_contract_map);
90
91    let raw_libraries: Vec<ContractIR> = if has_primary {
92        fallback
93            .iter()
94            .filter(|contract| matches!(contract.kind, ContractKind::Library))
95            // Built-in helper libraries (Runtime/Storage/Syscalls/Neo) are lowered directly during
96            // IR generation. Avoid merging their Solidity bodies into user contracts since they
97            // may contain EVM-only stubs or unsupported constructs, and they would bloat bytecode.
98            .filter(|contract| !is_builtin_library_name(contract.name.as_str()))
99            .cloned()
100            .collect()
101    } else {
102        Vec::new()
103    };
104
105    // Validate user libraries before merging. Convert each library to metadata
106    // and run the standard validation pipeline to catch library-specific errors
107    // (state variables, constructors, external functions) early.
108    //
109    // Cross-library struct references — e.g. `function executeInitReserve(
110    // ConfiguratorInputTypes.InitReserveInput calldata input)` declared in
111    // library `ConfiguratorLogic` and referencing a struct from library
112    // `ConfiguratorInputTypes` (both shipped in @aave/core-v3) — require that
113    // each library's validation pass see the structs declared in its peers.
114    // Otherwise `NeoType::from_solidity` can't resolve the qualified type,
115    // `param.neo_type` stays `None`, and the external-function check fires a
116    // spurious "uses unsupported type" error.
117    //
118    // We solve this by pre-merging every other library's structs (and enums,
119    // for symmetry) into each library's struct table before running its
120    // validation. Doing the merge here (instead of at flatten time) keeps the
121    // mutation scoped to a clone and means downstream stages still see the
122    // original, un-merged library tree.
123    let library_struct_pool: Vec<StructIR> = raw_libraries
124        .iter()
125        .flat_map(|lib| lib.structs.iter().cloned())
126        .collect();
127    let library_enum_pool: Vec<EnumIR> = raw_libraries
128        .iter()
129        .flat_map(|lib| lib.enums.iter().cloned())
130        .collect();
131    for lib in &raw_libraries {
132        let mut lib_with_peers = lib.clone();
133        for s in &library_struct_pool {
134            if !lib_with_peers.structs.iter().any(|own| own.name == s.name) {
135                lib_with_peers.structs.push(s.clone());
136            }
137        }
138        for e in &library_enum_pool {
139            if !lib_with_peers.enums.iter().any(|own| own.name == e.name) {
140                lib_with_peers.enums.push(e.clone());
141            }
142        }
143        // Run normalize first so the validation sees the post-merge
144        // semantics — library external functions get converted to internal
145        // BEFORE validate enforces "no storage parameter on external
146        // functions". Otherwise the validator rejects legitimate library
147        // patterns like `EModeLogic.executeSetUserEMode(mapping storage, ...)`
148        // (Aave) where the function is `external` in source but operates as
149        // an internal helper on Neo (libraries inline into their callers).
150        let normalized_lib = normalize_library_for_neo(lib_with_peers);
151        let lib_metadata = convert_contract(
152            normalized_lib,
153            &[],
154            &contract_types,
155            std::sync::Arc::new(SelectorRegistry::default()),
156        );
157        let lib_diagnostics = validate_contract(&lib_metadata);
158        let lib_errors: Vec<Diagnostic> = lib_diagnostics
159            .into_iter()
160            .filter(|d| matches!(d.severity, DiagnosticSeverity::Error))
161            .collect();
162        if !lib_errors.is_empty() {
163            let messages: Vec<String> = lib_errors.iter().map(|d| {
164                let mut msg = d.message.clone();
165                if let Some(suggestion) = &d.suggestion {
166                    msg.push_str(&format!("\n  suggestion: {suggestion}"));
167                }
168                msg
169            }).collect();
170            return Err(SolidityError::analysis(messages.join("\n")));
171        }
172    }
173
174    let libraries: Vec<ContractIR> = raw_libraries
175        .into_iter()
176        .map(normalize_library_for_neo)
177        .collect();
178
179    // Task #83 — when a primary contract `A` runs `B b = new B(); b.foo();`
180    // the compiler emits a 20-byte zero placeholder for `b` and lowers
181    // `b.foo()` as `System.Contract.Call([0;20], "foo", flags, args)`. B's
182    // compiled body is a separate artifact, so without help the call would
183    // return `Null` and A's return value would silently go empty. Fix:
184    // merge every sibling primary's public/external functions that A
185    // references via `new X()` into A's own function table (name-preserving,
186    // host-wins-on-collision); the runtime then routes the zero-hash call
187    // through `self_method_offsets` — see the Task #83 branch in
188    // `execution_impl_part2_contract_call.rs`.
189    if has_primary {
190        let sibling_fn_map: std::collections::HashMap<String, Vec<FunctionIR>> = primary
191            .iter()
192            .map(|c| {
193                (
194                    c.name.clone(),
195                    c.functions
196                        .iter()
197                        .filter(|f| {
198                            // Include abstract internal function declarations
199                            // (body = None) alongside concrete externals.
200                            // When sibling-merge pulls in an external body
201                            // like `rawFulfillRandomWords` whose body calls
202                            // an abstract sibling-internal function (e.g.
203                            // VRFConsumerBaseV2's `fulfillRandomWords(uint256,
204                            // uint256[])`), the host's IR-lowering pass would
205                            // otherwise fail the overload lookup with
206                            // "no overload of 'fulfillRandomWords' with 2
207                            // argument(s)". Importing the abstract declaration
208                            // satisfies the lookup; at runtime the call lands
209                            // on an empty stub (RET-only) which is acceptable
210                            // for the dead-code paths that typically include
211                            // such helpers transitively.
212                            let is_abstract_internal = matches!(f.ty, FunctionTy::Function)
213                                && f.body.is_none();
214                            // Task #126 — include Fallback (and Receive) alongside
215                            // ordinary external/public named functions so that a
216                            // primary contract whose only entrypoint is
217                            // `fallback()` still contributes its dispatcher to
218                            // the caller's merged function table when the caller
219                            // invokes a method the callee doesn't declare.
220                            //
221                            // Without this, `try Target(t).nonExistentMethod()`
222                            // (where TargetImpl only defines `fallback()`)
223                            // would never be able to route through the zero-
224                            // placeholder self-offsets path: the fallback entry
225                            // simply wouldn't be in the merge set, and the
226                            // runtime's unknown-method path would silently
227                            // return Null rather than propagating the fallback's
228                            // revert back to the caller's catch clause.
229                            let is_named_external = matches!(f.ty, FunctionTy::Function)
230                                && matches!(
231                                    f.visibility,
232                                    VisibilityKind::External | VisibilityKind::Public
233                                );
234                            let is_fallback_like = matches!(
235                                f.ty,
236                                FunctionTy::Fallback | FunctionTy::Receive
237                            );
238                            // (We deliberately do NOT include
239                            // `is_abstract_internal` here. Including abstract
240                            // internal declarations would let merged bodies
241                            // reference them, but it also fails the "all
242                            // abstract methods implemented" validation since
243                            // those functions don't have a body in the host.
244                            // We handle the missing-overload case at
245                            // IR-lowering time by emitting a runtime trap
246                            // instead of a compile error.)
247                            let _ = is_abstract_internal;
248                            is_named_external || is_fallback_like
249                        })
250                        .cloned()
251                        .collect::<Vec<_>>(),
252                )
253            })
254            .collect();
255        // Companion map: every sibling's modifier definitions — INCLUDING
256        // modifiers reachable through the sibling's inheritance chain. When
257        // sibling functions are merged into a host below, any modifier they
258        // apply (`function upgrade(...) public payable virtual onlyOwner`)
259        // must still resolve in the host. The host's local `modifier_defs`
260        // only sees ITS OWN modifier declarations — so without a parallel
261        // merge pass, merged `upgrade`'s `onlyOwner` lookup fails with
262        // "unresolved modifier 'onlyOwner' with 0 argument(s)". Repro: OZ
263        // TransparentUpgradeableProxy / ProxyAdmin import cycle, where
264        // ProxyAdmin's `onlyOwner` lives in its base contract Ownable.
265        //
266        // Because the sibling's own inheritance flattening hasn't happened
267        // yet at this point in the pipeline, we walk each sibling's
268        // linearized base chain ourselves and union their modifier
269        // definitions per sibling, keyed by (name, arity), preferring
270        // bodied modifiers over abstract declarations.
271        let sibling_modifier_map: std::collections::HashMap<String, Vec<FunctionIR>> = primary
272            .iter()
273            .map(|c| {
274                let mut seen: std::collections::HashMap<(String, usize), FunctionIR> =
275                    std::collections::HashMap::new();
276                let mut visit = |contract: &ContractIR| {
277                    for f in &contract.functions {
278                        if !matches!(f.ty, FunctionTy::Modifier) {
279                            continue;
280                        }
281                        let key = (f.name.clone(), f.parameters.len());
282                        match seen.get(&key) {
283                            Some(existing) if existing.body.is_some() => {}
284                            _ => {
285                                seen.insert(key, f.clone());
286                            }
287                        }
288                    }
289                };
290                visit(c);
291                // Try to walk the linearization. If it fails (shouldn't —
292                // we already ran it during pre-merge analysis to detect
293                // cycles), fall back to direct base inspection.
294                if let Ok(chain) =
295                    contract_linearization_base_to_derived(&c.name, &pre_merge_contract_map)
296                {
297                    for ancestor_name in &chain {
298                        if ancestor_name == &c.name {
299                            continue;
300                        }
301                        if let Some(ancestor) = pre_merge_contract_map.get(ancestor_name) {
302                            visit(ancestor);
303                        }
304                    }
305                }
306                (c.name.clone(), seen.into_values().collect::<Vec<_>>())
307            })
308            .collect();
309        // Task #197 — parallel state-variable map. When a sibling's external
310        // function body references state variables (e.g. Mock.balanceOf
311        // reading `_bal[a]`), merging only the FunctionIR leaves the
312        // identifier unresolved in the caller's `state_index_map`, so the
313        // variable lowering falls through to the `Integer(0)` placeholder
314        // path (src/ir/expressions/variable.rs) and downstream opcodes like
315        // SIZE/PICKITEM fault on the wrong StackItem type. The storage key
316        // is derived from the state variable's name (see
317        // `value_types.rs::compute_state_slot`), so merging Mock's `_bal`
318        // into Client lets Client's compiled balanceOf read the same
319        // storage slot Mock.mint wrote to.
320        let sibling_state_map: std::collections::HashMap<String, Vec<StateVariableIR>> =
321            primary
322                .iter()
323                .map(|c| (c.name.clone(), c.state_variables.clone()))
324                .collect();
325        // Task #198 — parallel constructor map. For `new Child(x, y)` inside a
326        // Parent contract, the compiled Child lives in a separate artifact, so
327        // the Parent's runtime invocation of its own `_deploy` never runs the
328        // Child constructor. Without executing the ctor body, Child's state
329        // variables (`a`, `b`) stay at their zero defaults — and the follow-up
330        // `c.a()` / `c.b()` cross-contract calls (routed through sibling-merge
331        // self-offsets; Task #83) therefore observe zeros.
332        //
333        // Fix: expose each sibling's constructor as a regular, internal,
334        // name-mangled function (`__ctor__<SiblingName>`) in the caller's
335        // merged function table. The `new Child(x, y)` lowering then calls
336        // `__ctor__Child(x, y)` in-line, running the ctor body against the
337        // already-merged sibling state-variable slots (Task #197). The address
338        // return value stays at the 20-byte zero placeholder that Task #83
339        // already routes to self-offsets dispatch.
340        let sibling_ctor_map: std::collections::HashMap<String, FunctionIR> = primary
341            .iter()
342            .filter_map(|c| {
343                let ctor = c
344                    .functions
345                    .iter()
346                    .find(|f| matches!(f.ty, FunctionTy::Constructor))?;
347                Some((c.name.clone(), ctor.clone()))
348            })
349            .collect();
350        let primary_contract_map: std::collections::HashMap<String, ContractIR> = primary
351            .iter()
352            .map(|c| (c.name.clone(), c.clone()))
353            .collect();
354        let primary_names: std::collections::HashSet<String> =
355            primary.iter().map(|c| c.name.clone()).collect();
356
357        // Task #126 — a primary contract's `fallback()` acts as a universal
358        // catch-all dispatcher: every unknown method name falls through to
359        // it. For interface-cast routing `Target(t).someMethod()` where
360        // `TargetImpl` has only `fallback()` (no named external methods),
361        // we must still treat `TargetImpl` as a valid implementor of the
362        // `Target` interface so the sibling-merge pass pulls its fallback
363        // body into the caller's function table. This mirrors Solidity's
364        // own runtime semantics: the ABI dispatcher routes unknown
365        // selectors to `fallback()` when present.
366        let primary_has_fallback: std::collections::HashSet<String> = primary
367            .iter()
368            .filter(|c| {
369                c.functions
370                    .iter()
371                    .any(|f| matches!(f.ty, FunctionTy::Fallback))
372            })
373            .map(|c| c.name.clone())
374            .collect();
375
376        // Task #115 — collect interface kind names and their external method
377        // sets. An expression like `I(t).getR()` in contract `C` (where `I`
378        // is an interface declared in the same source unit) is a
379        // cross-contract call routed through an `address`-typed receiver.
380        // At runtime the 20-byte zero placeholder triggers self-offsets
381        // dispatch (see `handle_contract_call` / Task #83 branch), so the
382        // callee method must live in the caller's merged function table.
383        // We match the interface to any sibling primary whose public/external
384        // method set is a superset of the interface's method names, and
385        // include those siblings in the sibling merge below.
386        //
387        // This mirrors the `new B()` / `B(addr)` / `B public b;` patterns
388        // already handled — without this hook, interface-typed dispatch
389        // silently returns `Null` (the `invoke_native_contract` fallback for
390        // unknown-hash calls), which then blows up inside `r.a` /
391        // `r.b` member accesses downstream.
392        let interface_methods: std::collections::HashMap<
393            String,
394            std::collections::HashSet<String>,
395        > = pre_merge_contract_map
396            .values()
397            .filter(|c| matches!(c.kind, ContractKind::Interface))
398            .map(|c| {
399                (
400                    c.name.clone(),
401                    c.functions
402                        .iter()
403                        .filter(|f| {
404                            matches!(f.ty, FunctionTy::Function)
405                                && matches!(
406                                    f.visibility,
407                                    VisibilityKind::External | VisibilityKind::Public
408                                )
409                        })
410                        .map(|f| f.name.clone())
411                        .collect(),
412                )
413            })
414            .collect();
415
416        // Reverse map: interface name → list of primary contracts whose
417        // method set covers the interface's method set. We pre-compute this
418        // once so we don't re-walk the primary function tables per function
419        // body.
420        let primary_method_names: std::collections::HashMap<
421            String,
422            std::collections::HashSet<String>,
423        > = primary
424            .iter()
425            .map(|c| {
426                (
427                    c.name.clone(),
428                    c.functions
429                        .iter()
430                        .filter(|f| {
431                            matches!(f.ty, FunctionTy::Function)
432                                && matches!(
433                                    f.visibility,
434                                    VisibilityKind::External | VisibilityKind::Public
435                                )
436                        })
437                        .map(|f| f.name.clone())
438                        .collect(),
439                )
440            })
441            .collect();
442
443        let interface_impls: std::collections::HashMap<String, Vec<String>> =
444            interface_methods
445                .iter()
446                .map(|(iface_name, iface_method_set)| {
447                    let mut impls: Vec<String> = primary_method_names
448                        .iter()
449                        .filter_map(|(prim_name, prim_set)| {
450                            // Task #126 — a primary with a `fallback()` catches
451                            // any interface method that isn't explicitly declared,
452                            // so it's always a valid implementor for sibling-
453                            // merge purposes (at runtime the call routes through
454                            // the merged `fallback` entry, which may itself
455                            // revert — that revert is what we propagate to the
456                            // caller's try/catch).
457                            if iface_method_set.is_subset(prim_set)
458                                || primary_has_fallback.contains(prim_name)
459                            {
460                                Some(prim_name.clone())
461                            } else {
462                                None
463                            }
464                        })
465                        .collect();
466                    // Deterministic order → reproducible bytecode offsets.
467                    impls.sort();
468                    (iface_name.clone(), impls)
469                })
470                .collect();
471
472        let interface_names: std::collections::HashSet<String> =
473            interface_methods.keys().cloned().collect();
474
475        for contract in primary.iter_mut() {
476            let mut referenced: std::collections::HashSet<String> =
477                std::collections::HashSet::new();
478            let mut iface_refs: std::collections::HashSet<String> =
479                std::collections::HashSet::new();
480            // Task #194 — collect method names statically resolvable from
481            // low-level-call payloads like
482            // `addr.call(abi.encodeWithSelector(bytes4(keccak256("m(T)"))))`,
483            // `addr.call(abi.encodeWithSignature("m(T)", …))`, or
484            // `addr.call(abi.encodeCall(Iface.m, …))`. Previously the
485            // sibling-merge pass only detected references through `new X()`,
486            // `X(addr)` casts, interface casts, and typed params/returns/
487            // state-vars. A low-level `.call()` with a constant selector is
488            // semantically identical to a typed `X(addr).m(…)` — the
489            // compiler routes it through the zero-placeholder
490            // `self_method_offsets` dispatch (see Task #83) — but without
491            // this scan the target method never lands in the merged table
492            // and the call silently returns `Null`.
493            let mut low_level_method_refs: std::collections::HashSet<String> =
494                std::collections::HashSet::new();
495            for function in &contract.functions {
496                if let Some(body) = function.body.as_ref() {
497                    collect_new_contract_refs(body, &primary_names, &mut referenced);
498                    // Task #115 — interface casts `I(expr)` in statements.
499                    collect_interface_casts_stmt(body, &interface_names, &mut iface_refs);
500                    // Task #194 — low-level calls whose payload encodes a
501                    // statically resolvable method name.
502                    collect_low_level_call_method_refs_stmt(
503                        body,
504                        &mut low_level_method_refs,
505                    );
506                }
507                // Task K4 — function params/returns typed as a sibling contract
508                // (e.g. `function bounce() external returns (B) {...}`, or
509                // `function xfer(C to, ...)`) mean the function is wired to
510                // call into the sibling. Merge so self-call routing can see
511                // the target method at runtime.
512                for p in function.parameters.iter().chain(function.returns.iter()) {
513                    if primary_names.contains(&p.ty) {
514                        referenced.insert(p.ty.clone());
515                    }
516                    // Task #115 — also scan for interface-typed parameters.
517                    if interface_names.contains(&p.ty) {
518                        iface_refs.insert(p.ty.clone());
519                    }
520                }
521            }
522            // Task K4 — also scan state-variable types and initializers.
523            // `B public b;` means A is wired to call into B via the storage
524            // slot without ever going through `new B()`. Without this hook,
525            // K4 (cross-contract reentrancy) fails: `b.bounce()` routes
526            // through `System.Contract.Call([0;20], "bounce", …)` which then
527            // returns `Null` because B wasn't merged.
528            for state in &contract.state_variables {
529                if primary_names.contains(&state.ty) {
530                    referenced.insert(state.ty.clone());
531                }
532                if interface_names.contains(&state.ty) {
533                    iface_refs.insert(state.ty.clone());
534                }
535                if let Some(init) = state.initializer.as_ref() {
536                    collect_new_refs_expr(init, &primary_names, &mut referenced);
537                    collect_interface_casts_expr(init, &interface_names, &mut iface_refs);
538                    collect_low_level_call_method_refs_expr(
539                        init,
540                        &mut low_level_method_refs,
541                    );
542                }
543            }
544            // Task #115 — expand interface references to the primary contracts
545            // that implement them. Multiple primaries may satisfy the same
546            // interface; merge all of them so dispatch sees any signature.
547            for iface in &iface_refs {
548                if let Some(impls) = interface_impls.get(iface) {
549                    for prim in impls {
550                        if prim != &contract.name {
551                            referenced.insert(prim.clone());
552                        }
553                    }
554                }
555            }
556            // Task #194 — expand low-level-call method references to every
557            // sibling primary that declares a method of that name. When
558            // multiple siblings satisfy the same name (e.g. both X and Y
559            // declare `foo()`), merge all of them so the dispatcher sees the
560            // union; which one actually fires at runtime is decided by the
561            // caller's address (handled in `handle_contract_call`). We skip
562            // the host contract itself — its methods are already visible
563            // through normal dispatch.
564            if !low_level_method_refs.is_empty() {
565                for (prim_name, prim_methods) in &primary_method_names {
566                    if prim_name == &contract.name {
567                        continue;
568                    }
569                    if low_level_method_refs
570                        .iter()
571                        .any(|m| prim_methods.contains(m))
572                    {
573                        referenced.insert(prim_name.clone());
574                    }
575                }
576            }
577            // Task #206 — close over TRANSITIVE sibling references. The
578            // zero-hash self-dispatch table is built from the caller
579            // artifact's manifest only, so if `Client` references `Middle`
580            // and `Middle` references `Target`, the merged `Client`
581            // artifact must carry both `wrap` and `fail`. Without this
582            // closure, the grandchild call silently falls through
583            // `handle_contract_call`'s zero-hash branch and returns `Null`.
584            let mut transitive_queue: Vec<String> = referenced.iter().cloned().collect();
585            while let Some(sibling_name) = transitive_queue.pop() {
586                let Some(sibling_contract) = primary_contract_map.get(&sibling_name) else {
587                    continue;
588                };
589                let transitive_refs = collect_direct_sibling_contract_refs(
590                    sibling_contract,
591                    &primary_names,
592                    &interface_names,
593                    &interface_impls,
594                    &primary_method_names,
595                );
596                for transitive in transitive_refs {
597                    if transitive == contract.name {
598                        continue;
599                    }
600                    if referenced.insert(transitive.clone()) {
601                        transitive_queue.push(transitive);
602                    }
603                }
604            }
605            referenced.remove(&contract.name);
606            if referenced.is_empty() {
607                continue;
608            }
609            let mut existing_sigs: std::collections::HashSet<(String, usize)> = contract
610                .functions
611                .iter()
612                .map(|f| (f.name.clone(), f.parameters.len()))
613                .collect();
614            // Deterministic order → reproducible bytecode offsets.
615            let mut sibling_names: Vec<String> = referenced.into_iter().collect();
616            sibling_names.sort();
617            for sibling_name in &sibling_names {
618                let Some(sibling_fns) = sibling_fn_map.get(sibling_name) else {
619                    continue;
620                };
621                for sibling_fn in sibling_fns {
622                    let sig = (sibling_fn.name.clone(), sibling_fn.parameters.len());
623                    if existing_sigs.insert(sig) {
624                        contract.functions.push(sibling_fn.clone());
625                    }
626                }
627                // Pull in the sibling's modifier definitions so the merged
628                // external bodies can still resolve their `onlyOwner` /
629                // `onlyRole` / etc. references when the host's modifier-
630                // expansion pass runs. Modifiers don't conflict on plain
631                // function-signature equality (different `ty`), so we track
632                // them in a small local set keyed on (name, arity).
633                if let Some(sibling_modifiers) = sibling_modifier_map.get(sibling_name) {
634                    for sibling_mod in sibling_modifiers {
635                        let already_present = contract.functions.iter().any(|existing| {
636                            matches!(existing.ty, FunctionTy::Modifier)
637                                && existing.name == sibling_mod.name
638                                && existing.parameters.len() == sibling_mod.parameters.len()
639                        });
640                        if !already_present {
641                            contract.functions.push(sibling_mod.clone());
642                        }
643                    }
644                }
645                // Also pull in the sibling's `using` directives. Merged
646                // external bodies may rely on contract-scope `using L for T;`
647                // declarations declared in the sibling — e.g. Gnosis Safe
648                // declares `using SafeMath for uint256;` and its
649                // `execTransaction` body calls `gas.max(other)`. When
650                // CompatibilityFallbackHandler triggers a sibling-merge of
651                // Safe's external methods, the `execTransaction` body needs
652                // its `using SafeMath` directive to remain in scope or the
653                // IR-lowering pass reports "member-style call '...' requires
654                // an explicit `using` directive". We dedup on
655                // (target_type, function_names) so reinjected duplicates
656                // don't grow the table.
657                if let Some(sibling_contract) = pre_merge_contract_map.get(sibling_name) {
658                    for directive in &sibling_contract.using_directives {
659                        if !contract.using_directives.iter().any(|existing| {
660                            existing.target_type == directive.target_type
661                                && existing.function_names == directive.function_names
662                        }) {
663                            contract.using_directives.push(directive.clone());
664                        }
665                    }
666                    for lib_name in &sibling_contract.using_for_libraries {
667                        if !contract.using_for_libraries.contains(lib_name) {
668                            contract.using_for_libraries.push(lib_name.clone());
669                        }
670                    }
671                    contract.has_using_for_star =
672                        contract.has_using_for_star || sibling_contract.has_using_for_star;
673                    contract.has_using_function_list = contract.has_using_function_list
674                        || sibling_contract.has_using_function_list;
675                }
676            }
677
678            // Task #197 — merge sibling state variables after their external
679            // functions. Without this, a merged stateful method like
680            // `Mock.balanceOf` → `return _bal[a]` would resolve `_bal`
681            // against the caller's (Client's) `state_index_map`, find no
682            // match, and fall through `variable.rs::lower_variable_expression`'s
683            // final compatibility arm which pushes `Integer(0)` as a neutral
684            // placeholder. Downstream SIZE/PICKITEM opcodes then fault on
685            // the scalar, surfacing as "SIZE: unsupported type" at runtime.
686            //
687            // Storage-key derivation is name-based (see
688            // `value_types.rs::compute_state_slot`), so merging Mock's
689            // `_bal` into Client produces the same keccak-derived slot
690            // that Mock.mint writes to — the cross-contract read lands on
691            // the same storage entry. Host-wins-on-collision preserves any
692            // state variable the caller already declares.
693            let mut existing_state_names: std::collections::HashSet<String> = contract
694                .state_variables
695                .iter()
696                .filter_map(|s| s.name.clone())
697                .collect();
698            for sibling_name in &sibling_names {
699                let Some(sibling_states) = sibling_state_map.get(sibling_name) else {
700                    continue;
701                };
702                for sibling_state in sibling_states {
703                    if let Some(name) = sibling_state.name.as_ref() {
704                        if existing_state_names.insert(name.clone()) {
705                            contract.state_variables.push(sibling_state.clone());
706                        } else if let Some(existing) = contract
707                            .state_variables
708                            .iter()
709                            .find(|s| s.name.as_deref() == Some(name.as_str()))
710                        {
711                            // Storage-soundness guard — slots are derived
712                            // from the BARE variable name (`sha256(name)`,
713                            // see `storage_key::compute_state_slot`), so a
714                            // host/sibling pair declaring the same name with
715                            // DIFFERENT types would silently collapse two
716                            // semantically distinct lvalues onto one slot
717                            // (e.g. a sibling's `mapping(address=>uint256)
718                            // _bal` aliasing the host's scalar `uint256
719                            // _bal`), miscompiling both. Same-name SAME-type
720                            // sharing stays allowed: that is the documented
721                            // Task #197 design (merged sibling bodies must
722                            // hit the same name-keyed slot).
723                            if normalize_state_type_for_merge(&existing.ty)
724                                != normalize_state_type_for_merge(&sibling_state.ty)
725                            {
726                                return Err(SolidityError::analysis(format!(
727                                    "state variable '{name}' is declared with conflicting types \
728                                     across merged contracts: '{}' in '{}' vs '{}' in '{sibling_name}'. \
729                                     Storage slots are derived from the bare variable name, so both \
730                                     declarations would silently alias the same storage entry; \
731                                     rename one of the variables.",
732                                    existing.ty, contract.name, sibling_state.ty
733                                )));
734                            }
735                        }
736                    }
737                }
738            }
739
740            // Task #198 — merge sibling constructors as name-mangled internal
741            // regular functions so the caller's `new Child(args)` lowering can
742            // invoke the ctor body in-line. Without this, `new Child(x, y)`
743            // silently drops its args and the follow-up `c.a()` / `c.b()`
744            // reads land on uninitialized storage (all zeros). Re-typing from
745            // `Constructor` to `Function` prevents the caller's own `_deploy`
746            // prologue from accidentally calling the merged entry at deploy
747            // time (constructor_indices is populated by FunctionKind, so
748            // Regular entries are skipped there).
749            for sibling_name in &sibling_names {
750                let Some(sibling_ctor) = sibling_ctor_map.get(sibling_name) else {
751                    continue;
752                };
753                let mangled_name = format!("__ctor__{sibling_name}");
754                let sig = (mangled_name.clone(), sibling_ctor.parameters.len());
755                if !existing_sigs.insert(sig) {
756                    continue;
757                }
758                let mut cloned = sibling_ctor.clone();
759                cloned.name = mangled_name;
760                cloned.ty = FunctionTy::Function;
761                cloned.visibility = VisibilityKind::Internal;
762                // Base-constructor invocations (`base_or_modifiers`) were
763                // already resolved by `apply_modifiers_and_base_constructors`
764                // in the owning contract's pipeline; clear the residue so the
765                // caller's modifier-application pass (which ran before this
766                // merge) doesn't re-expand anything.
767                cloned.base_or_modifiers.clear();
768                contract.functions.push(cloned);
769            }
770        }
771    }
772
773    // Make non-inherited enum/struct namespaces visible across compilation
774    // units so expressions like `Enum.Operation.DelegateCall` can resolve even
775    // when the defining type lives in another top-level contract/library file.
776    if has_primary {
777        let shared_type_defs: Vec<(String, Vec<StructIR>, Vec<EnumIR>)> = pre_merge_contract_map
778            .values()
779            .filter(|contract| {
780                !matches!(contract.kind, ContractKind::Library)
781                    || !is_builtin_library_name(contract.name.as_str())
782            })
783            .map(|contract| {
784                (
785                    contract.name.clone(),
786                    contract.structs.clone(),
787                    contract.enums.clone(),
788                )
789            })
790            .collect();
791
792        for contract in primary.iter_mut() {
793            let mut seen_structs: std::collections::HashSet<String> = contract
794                .structs
795                .iter()
796                .map(|item| item.name.to_ascii_lowercase())
797                .collect();
798            let mut seen_enums: std::collections::HashSet<String> = contract
799                .enums
800                .iter()
801                .map(|item| item.name.to_ascii_lowercase())
802                .collect();
803
804            for (owner_name, structs, enums) in &shared_type_defs {
805                if owner_name == &contract.name {
806                    continue;
807                }
808                for item in structs {
809                    let key = item.name.to_ascii_lowercase();
810                    if seen_structs.insert(key) {
811                        contract.structs.push(item.clone());
812                    }
813                }
814                for item in enums {
815                    let key = item.name.to_ascii_lowercase();
816                    if seen_enums.insert(key) {
817                        contract.enums.push(item.clone());
818                    }
819                }
820            }
821        }
822    }
823
824    // Build a lookup map for inheritance flattening and modifier expansion.
825    let contract_map: std::collections::HashMap<String, ContractIR> = primary
826        .iter()
827        .chain(fallback.iter())
828        .map(|contract| (contract.name.clone(), contract.clone()))
829        .collect();
830
831    // Task #106 — gather struct fields across all contracts so canonical
832    // signatures can expand struct params into their `(field1,field2,...)` tuple
833    // form per the EVM ABI spec. Without this, the selector for
834    // `f(P memory p)` where `struct P { uint256 a; bool b; }` is computed from
835    // `f(P)` — which does not match the Solidity-spec selector for
836    // `f((uint256,bool))`.
837    let mut struct_fields_map: std::collections::HashMap<
838        String,
839        Vec<(String, String)>,
840    > = std::collections::HashMap::new();
841    for contract in contract_map.values() {
842        for struct_def in &contract.structs {
843            let entries: Vec<(String, String)> = struct_def
844                .fields
845                .iter()
846                .map(|f| (f.name.clone(), f.ty.clone()))
847                .collect();
848            struct_fields_map
849                .entry(struct_def.name.clone())
850                .or_insert(entries);
851        }
852    }
853
854    // Build a shared selector registry so `.selector` expressions can resolve against
855    // any contract/interface visible to this compilation unit (including those defined
856    // after the primary contract in the same file).
857    // Every visible type name (contract/interface/library) — contract-typed
858    // params resolve to `address` for ABI canonicalization.
859    let registry_contract_types: Vec<String> =
860        contract_map.values().map(|c| c.name.clone()).collect();
861    let mut type_method_selectors: std::collections::HashMap<
862        String,
863        std::collections::HashMap<String, Vec<[u8; 4]>>,
864    > = std::collections::HashMap::new();
865    let mut interface_types: std::collections::HashSet<String> = std::collections::HashSet::new();
866    for contract in contract_map.values() {
867        if matches!(contract.kind, ContractKind::Interface) {
868            interface_types.insert(contract.name.clone());
869        }
870
871        // When building selector lookups for `.selector` / `.interfaceId`, include inherited
872        // interface methods as part of the derived interface. This matches Solidity behavior
873        // and supports patterns like `type(IChild).interfaceId` where `IChild is IParent`.
874        let selector_contract = match contract.kind {
875            ContractKind::Contract | ContractKind::AbstractContract | ContractKind::Interface => {
876                flatten_contract_inheritance(contract.clone(), &contract_map)
877                    .map(|(ir, _warnings)| ir)
878                    .unwrap_or_else(|_| contract.clone())
879            }
880            ContractKind::Library => contract.clone(),
881        };
882
883        let mut per_type: std::collections::HashMap<String, Vec<[u8; 4]>> =
884            std::collections::HashMap::new();
885
886        // Resolve each `.selector` parameter through the SAME canonicalization as
887        // the manifest selector (`FunctionMetadata.selector`, built via
888        // `NeoType::canonical_abi_type` in convert/functions.rs): structs expand to
889        // tuples, enums render as `uint8`, integer widths are explicit. The two
890        // paths must produce identical selectors — both drive on-chain dispatch and
891        // a contract's `this.f.selector` must match what external callers compute.
892        let sel_struct_types: Vec<StructTypeMetadata> = selector_contract
893            .structs
894            .iter()
895            .map(|s| StructTypeMetadata {
896                name: s.name.clone(),
897                fields: s
898                    .fields
899                    .iter()
900                    .map(|f| NeoStructFieldMetadata {
901                        name: f.name.clone(),
902                        ty: f.ty.clone(),
903                    })
904                    .collect(),
905            })
906            .collect();
907        let sel_enum_types: Vec<EnumTypeMetadata> = selector_contract
908            .enums
909            .iter()
910            .map(|e| EnumTypeMetadata {
911                name: e.name.clone(),
912                variants: e.values.len(),
913            })
914            .collect();
915
916        for function in &selector_contract.functions {
917            if !matches!(function.ty, FunctionTy::Function) {
918                continue;
919            }
920
921            if !matches!(
922                function.visibility,
923                VisibilityKind::External | VisibilityKind::Public
924            ) {
925                continue;
926            }
927
928            let param_signatures: Vec<String> = function
929                .parameters
930                .iter()
931                .map(|param| {
932                    match NeoType::from_solidity(
933                        &param.ty,
934                        &sel_struct_types,
935                        &sel_enum_types,
936                        &registry_contract_types,
937                    ) {
938                        Ok(neo_type) => neo_type.canonical_abi_type(),
939                        // Fall back to the struct-aware string canonicalizer only
940                        // when the type cannot be resolved (keeps prior behavior).
941                        Err(_) => crate::utils::canonical_param_type_with_structs(
942                            &param.ty,
943                            &struct_fields_map,
944                        ),
945                    }
946                })
947                .collect();
948            let selector = compute_function_selector(&function.name, &param_signatures);
949            per_type
950                .entry(function.name.clone())
951                .or_default()
952                .push(selector);
953        }
954
955        type_method_selectors.insert(contract.name.clone(), per_type);
956    }
957    let selector_registry = std::sync::Arc::new(SelectorRegistry {
958        type_method_selectors,
959        interface_types,
960    });
961
962    let mut selected = if has_primary { primary } else { fallback };
963
964    if selected.is_empty() {
965        return Ok(Vec::new());
966    }
967
968    let mut metadatas = Vec::new();
969    for contract in selected.drain(..) {
970        let (mut flattened, flatten_warnings) =
971            flatten_contract_inheritance(contract, &contract_map)?;
972        // Merge user-defined libraries AFTER inheritance flattening so the
973        // flattener doesn't mistake cloned library helpers for inheritance
974        // overrides. The final flattened contract still needs the library
975        // helpers/types present before `convert_contract` so direct library
976        // calls and `using for` member-style calls lower correctly.
977        if has_primary && !libraries.is_empty() {
978            for lib in &libraries {
979                flattened.functions.extend(lib.functions.clone());
980                flattened.state_variables.extend(lib.state_variables.clone());
981                flattened.structs.extend(lib.structs.clone());
982                flattened.enums.extend(lib.enums.clone());
983                // Merge the library's own `using` directives into the host.
984                // Library function bodies are inlined verbatim above, so any
985                // member-style call resolved by a library-scope `using` (e.g.
986                // OZ Strings.sol declares `using SafeCast for *;` then calls
987                // `someBool.toUint()` inside its own helpers) must continue to
988                // resolve after the body lives inside the host contract.
989                // Without this, the IR-lowering pass at
990                // `src/ir/expressions/calls/member_calls.rs:432` reports
991                // "member-style call '...' requires an explicit `using`
992                // directive" for the inlined library code.
993                for directive in &lib.using_directives {
994                    if !flattened.using_directives.iter().any(|existing| {
995                        existing.target_type == directive.target_type
996                            && existing.function_names == directive.function_names
997                    }) {
998                        flattened.using_directives.push(directive.clone());
999                    }
1000                }
1001                for lib_name in &lib.using_for_libraries {
1002                    if !flattened.using_for_libraries.contains(lib_name) {
1003                        flattened.using_for_libraries.push(lib_name.clone());
1004                    }
1005                }
1006                flattened.has_using_for_star =
1007                    flattened.has_using_for_star || lib.has_using_for_star;
1008                flattened.has_using_function_list =
1009                    flattened.has_using_function_list || lib.has_using_function_list;
1010            }
1011        }
1012        apply_modifiers_and_base_constructors(&mut flattened, &contract_map)?;
1013        let mut metadata = convert_contract(
1014            flattened,
1015            &[],
1016            &contract_types,
1017            selector_registry.clone(),
1018        );
1019        metadata.flatten_warnings = flatten_warnings;
1020        metadatas.push(metadata);
1021    }
1022
1023    Ok(metadatas)
1024}
1025
1026/// Task #83 — walk a statement tree collecting every `new X()` target name
1027/// that matches a known primary contract. Mirrors the ast_scan permissions
1028/// pass but accumulates matches instead of returning a boolean.
1029fn collect_new_contract_refs(
1030    stmt: &Statement,
1031    primary_names: &std::collections::HashSet<String>,
1032    sink: &mut std::collections::HashSet<String>,
1033) {
1034    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1035        collect_new_contract_refs_inner(stmt, primary_names, sink)
1036    })
1037}
1038
1039fn collect_new_contract_refs_inner(
1040    stmt: &Statement,
1041    primary_names: &std::collections::HashSet<String>,
1042    sink: &mut std::collections::HashSet<String>,
1043) {
1044    match stmt {
1045        Statement::Block { statements, .. } => {
1046            for s in statements {
1047                collect_new_contract_refs(s, primary_names, sink);
1048            }
1049        }
1050        Statement::If(_, cond, t, e) => {
1051            collect_new_refs_expr(cond, primary_names, sink);
1052            collect_new_contract_refs(t, primary_names, sink);
1053            if let Some(s) = e {
1054                collect_new_contract_refs(s, primary_names, sink);
1055            }
1056        }
1057        Statement::While(_, cond, body) | Statement::DoWhile(_, body, cond) => {
1058            collect_new_refs_expr(cond, primary_names, sink);
1059            collect_new_contract_refs(body, primary_names, sink);
1060        }
1061        Statement::Expression(_, expr) => collect_new_refs_expr(expr, primary_names, sink),
1062        Statement::VariableDefinition(_, _, Some(expr)) => {
1063            collect_new_refs_expr(expr, primary_names, sink);
1064        }
1065        Statement::VariableDefinition(_, _, None) => {}
1066        Statement::For(_, i, c, n, b) => {
1067            if let Some(s) = i {
1068                collect_new_contract_refs(s, primary_names, sink);
1069            }
1070            if let Some(e) = c {
1071                collect_new_refs_expr(e, primary_names, sink);
1072            }
1073            if let Some(e) = n {
1074                collect_new_refs_expr(e, primary_names, sink);
1075            }
1076            if let Some(s) = b {
1077                collect_new_contract_refs(s, primary_names, sink);
1078            }
1079        }
1080        Statement::Return(_, Some(expr)) | Statement::Emit(_, expr) => {
1081            collect_new_refs_expr(expr, primary_names, sink);
1082        }
1083        Statement::Revert(_, _, args) => {
1084            for e in args {
1085                collect_new_refs_expr(e, primary_names, sink);
1086            }
1087        }
1088        Statement::Try(_, expr, returns, clauses) => {
1089            collect_new_refs_expr(expr, primary_names, sink);
1090            if let Some((_, b)) = returns {
1091                collect_new_contract_refs(b, primary_names, sink);
1092            }
1093            for c in clauses {
1094                match c {
1095                    CatchClause::Simple(_, _, b) | CatchClause::Named(_, _, _, b) => {
1096                        collect_new_contract_refs(b, primary_names, sink);
1097                    }
1098                }
1099            }
1100        }
1101        _ => {}
1102    }
1103}
1104
1105/// Task #115 — statement-level walk that collects every `I(expr).method(...)`
1106/// interface-cast receiver where `I` is a known interface declared in the
1107/// same source unit. Mirrors `collect_new_contract_refs` but tracks a
1108/// different alphabet of names (interface kinds, not primary contracts).
1109fn collect_interface_casts_stmt(
1110    stmt: &Statement,
1111    interface_names: &std::collections::HashSet<String>,
1112    sink: &mut std::collections::HashSet<String>,
1113) {
1114    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1115        collect_interface_casts_stmt_inner(stmt, interface_names, sink)
1116    })
1117}
1118
1119fn collect_interface_casts_stmt_inner(
1120    stmt: &Statement,
1121    interface_names: &std::collections::HashSet<String>,
1122    sink: &mut std::collections::HashSet<String>,
1123) {
1124    match stmt {
1125        Statement::Block { statements, .. } => {
1126            for s in statements {
1127                collect_interface_casts_stmt(s, interface_names, sink);
1128            }
1129        }
1130        Statement::If(_, cond, t, e) => {
1131            collect_interface_casts_expr(cond, interface_names, sink);
1132            collect_interface_casts_stmt(t, interface_names, sink);
1133            if let Some(s) = e {
1134                collect_interface_casts_stmt(s, interface_names, sink);
1135            }
1136        }
1137        Statement::While(_, cond, body) | Statement::DoWhile(_, body, cond) => {
1138            collect_interface_casts_expr(cond, interface_names, sink);
1139            collect_interface_casts_stmt(body, interface_names, sink);
1140        }
1141        Statement::Expression(_, expr) => {
1142            collect_interface_casts_expr(expr, interface_names, sink)
1143        }
1144        Statement::VariableDefinition(_, _, Some(expr)) => {
1145            collect_interface_casts_expr(expr, interface_names, sink);
1146        }
1147        Statement::VariableDefinition(_, _, None) => {}
1148        Statement::For(_, i, c, n, b) => {
1149            if let Some(s) = i {
1150                collect_interface_casts_stmt(s, interface_names, sink);
1151            }
1152            if let Some(e) = c {
1153                collect_interface_casts_expr(e, interface_names, sink);
1154            }
1155            if let Some(e) = n {
1156                collect_interface_casts_expr(e, interface_names, sink);
1157            }
1158            if let Some(s) = b {
1159                collect_interface_casts_stmt(s, interface_names, sink);
1160            }
1161        }
1162        Statement::Return(_, Some(expr)) | Statement::Emit(_, expr) => {
1163            collect_interface_casts_expr(expr, interface_names, sink);
1164        }
1165        Statement::Revert(_, _, args) => {
1166            for e in args {
1167                collect_interface_casts_expr(e, interface_names, sink);
1168            }
1169        }
1170        Statement::Try(_, expr, returns, clauses) => {
1171            collect_interface_casts_expr(expr, interface_names, sink);
1172            if let Some((_, b)) = returns {
1173                collect_interface_casts_stmt(b, interface_names, sink);
1174            }
1175            for c in clauses {
1176                match c {
1177                    CatchClause::Simple(_, _, b) | CatchClause::Named(_, _, _, b) => {
1178                        collect_interface_casts_stmt(b, interface_names, sink);
1179                    }
1180                }
1181            }
1182        }
1183        _ => {}
1184    }
1185}
1186
1187/// Task #115 — expression-level half of `collect_interface_casts_stmt`.
1188/// Matches `FunctionCall(Variable(I), _)` where `I` is a known interface
1189/// name. The parser emits this shape for interface casts like `I(addr)`.
1190fn collect_interface_casts_expr(
1191    expr: &Expression,
1192    interface_names: &std::collections::HashSet<String>,
1193    sink: &mut std::collections::HashSet<String>,
1194) {
1195    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1196        collect_interface_casts_expr_inner(expr, interface_names, sink)
1197    })
1198}
1199
1200fn collect_interface_casts_expr_inner(
1201    expr: &Expression,
1202    interface_names: &std::collections::HashSet<String>,
1203    sink: &mut std::collections::HashSet<String>,
1204) {
1205    if let Expression::FunctionCall(_, func, _) = expr {
1206        if let Expression::Variable(id) = func.as_ref() {
1207            if interface_names.contains(&id.name) {
1208                sink.insert(id.name.clone());
1209            }
1210        }
1211    }
1212    match expr {
1213        Expression::New(_, i)
1214        | Expression::Parenthesis(_, i)
1215        | Expression::MemberAccess(_, i, _)
1216        | Expression::Delete(_, i) => collect_interface_casts_expr(i, interface_names, sink),
1217        Expression::FunctionCall(_, func, args) => {
1218            collect_interface_casts_expr(func, interface_names, sink);
1219            for a in args {
1220                collect_interface_casts_expr(a, interface_names, sink);
1221            }
1222        }
1223        // Task #125 — symmetric fix with `collect_new_refs_expr`: the
1224        // `try-expr { success-block }` lowering parks the call inside a
1225        // FunctionCallBlock, so interface-cast chains such as
1226        // `try I(t).getR() returns (R r) { ... } catch ...` would also
1227        // silently skip the sibling-merge trigger without this arm.
1228        Expression::FunctionCallBlock(_, call, block) => {
1229            collect_interface_casts_expr(call, interface_names, sink);
1230            collect_interface_casts_stmt(block, interface_names, sink);
1231        }
1232        Expression::NamedFunctionCall(_, func, args) => {
1233            collect_interface_casts_expr(func, interface_names, sink);
1234            for a in args {
1235                collect_interface_casts_expr(&a.expr, interface_names, sink);
1236            }
1237        }
1238        Expression::ArraySubscript(_, a, b) => {
1239            collect_interface_casts_expr(a, interface_names, sink);
1240            if let Some(e) = b {
1241                collect_interface_casts_expr(e, interface_names, sink);
1242            }
1243        }
1244        Expression::ConditionalOperator(_, c, a, b) => {
1245            collect_interface_casts_expr(c, interface_names, sink);
1246            collect_interface_casts_expr(a, interface_names, sink);
1247            collect_interface_casts_expr(b, interface_names, sink);
1248        }
1249        Expression::Assign(_, a, b) => {
1250            collect_interface_casts_expr(a, interface_names, sink);
1251            collect_interface_casts_expr(b, interface_names, sink);
1252        }
1253        Expression::ArrayLiteral(_, values) => {
1254            for v in values {
1255                collect_interface_casts_expr(v, interface_names, sink);
1256            }
1257        }
1258        _ => {}
1259    }
1260}
1261
1262/// Task #83 — expression-level half of `collect_new_contract_refs`. Matches
1263/// `Expression::New(FunctionCall(Variable(name), _))` and recurses through
1264/// the usual expression containers.
1265fn collect_new_refs_expr(
1266    expr: &Expression,
1267    primary_names: &std::collections::HashSet<String>,
1268    sink: &mut std::collections::HashSet<String>,
1269) {
1270    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1271        collect_new_refs_expr_inner(expr, primary_names, sink)
1272    })
1273}
1274
1275fn collect_new_refs_expr_inner(
1276    expr: &Expression,
1277    primary_names: &std::collections::HashSet<String>,
1278    sink: &mut std::collections::HashSet<String>,
1279) {
1280    if let Expression::New(_, inner) = expr {
1281        if let Expression::FunctionCall(_, func, _) = inner.as_ref() {
1282            if let Expression::Variable(id) = func.as_ref() {
1283                if primary_names.contains(&id.name) {
1284                    sink.insert(id.name.clone());
1285                }
1286            }
1287        }
1288    }
1289    // Task K4 — `B(addr)` cast expressions mean A plans to call into B
1290    // through an address typed as B. The parser lowers these as
1291    // `FunctionCall(Variable("B"), [addr])`, identical in shape to a
1292    // `B.staticCall(addr)` helper, so we match on that before the generic
1293    // FunctionCall recursion below picks off the args.
1294    if let Expression::FunctionCall(_, func, _) = expr {
1295        if let Expression::Variable(id) = func.as_ref() {
1296            if primary_names.contains(&id.name) {
1297                sink.insert(id.name.clone());
1298            }
1299        }
1300    }
1301    match expr {
1302        Expression::New(_, i)
1303        | Expression::Parenthesis(_, i)
1304        | Expression::MemberAccess(_, i, _)
1305        | Expression::Delete(_, i) => collect_new_refs_expr(i, primary_names, sink),
1306        Expression::FunctionCall(_, func, args) => {
1307            collect_new_refs_expr(func, primary_names, sink);
1308            for a in args {
1309                collect_new_refs_expr(a, primary_names, sink);
1310            }
1311        }
1312        // Task #125 — `try X { ... } catch ...` parses the leading
1313        // `try-expr { success-block }` as `FunctionCallBlock(call, block)`
1314        // on the expression-tree side, so `try Target(t).willRevert() { ... }`
1315        // arrives here with `call = FunctionCall(MemberAccess(FunctionCall(
1316        // Variable("Target"), [t]), "willRevert"), [])` wrapped in a
1317        // FunctionCallBlock. Without this arm the walker's `_ => {}`
1318        // silently dropped the cast chain, so `Target` never made the
1319        // sibling-merge `referenced` set and `willRevert` never entered
1320        // C's `self_method_offsets` table — the runtime's
1321        // `handle_contract_call` then fell through to `invoke_native_contract`
1322        // which returned `Null` for the zero-placeholder hash, so the
1323        // target's `revert("bad")` was never dispatched and the outer
1324        // try-arm fired with its literal "ok" instead of the expected
1325        // `catch Error(string)` binding. The success block body is a
1326        // Statement, not an Expression, so we use the statement walker
1327        // for it — symmetric with the `Statement::Try` arm above.
1328        Expression::FunctionCallBlock(_, call, block) => {
1329            collect_new_refs_expr(call, primary_names, sink);
1330            collect_new_contract_refs(block, primary_names, sink);
1331        }
1332        Expression::NamedFunctionCall(_, func, args) => {
1333            collect_new_refs_expr(func, primary_names, sink);
1334            for a in args {
1335                collect_new_refs_expr(&a.expr, primary_names, sink);
1336            }
1337        }
1338        Expression::ArraySubscript(_, a, b) => {
1339            collect_new_refs_expr(a, primary_names, sink);
1340            if let Some(e) = b {
1341                collect_new_refs_expr(e, primary_names, sink);
1342            }
1343        }
1344        Expression::ConditionalOperator(_, c, a, b) => {
1345            collect_new_refs_expr(c, primary_names, sink);
1346            collect_new_refs_expr(a, primary_names, sink);
1347            collect_new_refs_expr(b, primary_names, sink);
1348        }
1349        Expression::Assign(_, a, b) => {
1350            collect_new_refs_expr(a, primary_names, sink);
1351            collect_new_refs_expr(b, primary_names, sink);
1352        }
1353        Expression::ArrayLiteral(_, values) => {
1354            for v in values {
1355                collect_new_refs_expr(v, primary_names, sink);
1356            }
1357        }
1358        _ => {}
1359    }
1360}
1361
1362/// Task #194 — statement walker that collects statically resolvable method
1363/// names from low-level `addr.call(...)` / `addr.staticcall(...)` payloads.
1364/// Mirrors the shape of `collect_new_contract_refs` but feeds a different
1365/// alphabet: plain method names (e.g. `"getValue"`) that the sibling-merge
1366/// pass later cross-references against every sibling primary's declared
1367/// method set.
1368fn collect_low_level_call_method_refs_stmt(
1369    stmt: &Statement,
1370    sink: &mut std::collections::HashSet<String>,
1371) {
1372    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1373        collect_low_level_call_method_refs_stmt_inner(stmt, sink)
1374    })
1375}
1376
1377fn collect_low_level_call_method_refs_stmt_inner(
1378    stmt: &Statement,
1379    sink: &mut std::collections::HashSet<String>,
1380) {
1381    match stmt {
1382        Statement::Block { statements, .. } => {
1383            for s in statements {
1384                collect_low_level_call_method_refs_stmt(s, sink);
1385            }
1386        }
1387        Statement::If(_, cond, t, e) => {
1388            collect_low_level_call_method_refs_expr(cond, sink);
1389            collect_low_level_call_method_refs_stmt(t, sink);
1390            if let Some(s) = e {
1391                collect_low_level_call_method_refs_stmt(s, sink);
1392            }
1393        }
1394        Statement::While(_, cond, body) | Statement::DoWhile(_, body, cond) => {
1395            collect_low_level_call_method_refs_expr(cond, sink);
1396            collect_low_level_call_method_refs_stmt(body, sink);
1397        }
1398        Statement::Expression(_, expr) => {
1399            collect_low_level_call_method_refs_expr(expr, sink);
1400        }
1401        Statement::VariableDefinition(_, _, Some(expr)) => {
1402            collect_low_level_call_method_refs_expr(expr, sink);
1403        }
1404        Statement::VariableDefinition(_, _, None) => {}
1405        Statement::For(_, i, c, n, b) => {
1406            if let Some(s) = i {
1407                collect_low_level_call_method_refs_stmt(s, sink);
1408            }
1409            if let Some(e) = c {
1410                collect_low_level_call_method_refs_expr(e, sink);
1411            }
1412            if let Some(e) = n {
1413                collect_low_level_call_method_refs_expr(e, sink);
1414            }
1415            if let Some(s) = b {
1416                collect_low_level_call_method_refs_stmt(s, sink);
1417            }
1418        }
1419        Statement::Return(_, Some(expr)) | Statement::Emit(_, expr) => {
1420            collect_low_level_call_method_refs_expr(expr, sink);
1421        }
1422        Statement::Revert(_, _, args) => {
1423            for e in args {
1424                collect_low_level_call_method_refs_expr(e, sink);
1425            }
1426        }
1427        Statement::Try(_, expr, returns, clauses) => {
1428            collect_low_level_call_method_refs_expr(expr, sink);
1429            if let Some((_, b)) = returns {
1430                collect_low_level_call_method_refs_stmt(b, sink);
1431            }
1432            for c in clauses {
1433                match c {
1434                    CatchClause::Simple(_, _, b) | CatchClause::Named(_, _, _, b) => {
1435                        collect_low_level_call_method_refs_stmt(b, sink);
1436                    }
1437                }
1438            }
1439        }
1440        _ => {}
1441    }
1442}
1443
1444/// Task #194 — expression walker that recognises `<receiver>.call(payload)`
1445/// / `<receiver>.staticcall(payload)` / `<receiver>.delegatecall(payload)`
1446/// shapes, then peels the `abi.encodeWith{Selector,Signature}` /
1447/// `abi.encodeCall` wrapper on the payload to extract the Solidity method
1448/// name when it can be resolved at compile time. The extracted name is
1449/// later matched against every sibling primary's declared public/external
1450/// method set.
1451///
1452/// Static resolution handles:
1453///   - `abi.encodeWithSignature("m(T)", …)` — literal signature string,
1454///     name taken from the pre-`(` fragment.
1455///   - `abi.encodeWithSelector(bytes4(keccak256("m(T)")))` —
1456///     compile-time hash of a literal signature string.
1457///   - `abi.encodeWithSelector(Type.method.selector)` /
1458///     `abi.encodeCall(Type.method, (…))` — static member-access.
1459///
1460/// Runtime-computed selectors (e.g. `abi.encodeWithSelector(someRuntimeSel,
1461/// …)`) stay unresolved and yield nothing — the compiler's caller-side
1462/// lowering similarly cannot route those through sibling-merge, so they
1463/// fall through to the real cross-contract dispatch path.
1464fn collect_low_level_call_method_refs_expr(
1465    expr: &Expression,
1466    sink: &mut std::collections::HashSet<String>,
1467) {
1468    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1469        collect_low_level_call_method_refs_expr_inner(expr, sink)
1470    })
1471}
1472
1473fn collect_low_level_call_method_refs_expr_inner(
1474    expr: &Expression,
1475    sink: &mut std::collections::HashSet<String>,
1476) {
1477    if let Expression::FunctionCall(_, func, args) = expr {
1478        if args.len() == 1 {
1479            if let Expression::MemberAccess(_, _recv, member) = func.as_ref() {
1480                let is_low_level = matches!(
1481                    member.name.as_str(),
1482                    "call" | "staticcall" | "delegatecall"
1483                );
1484                if is_low_level {
1485                    if let Some(name) = extract_static_method_name_from_payload(&args[0]) {
1486                        if !name.trim().is_empty() {
1487                            sink.insert(name);
1488                        }
1489                    }
1490                }
1491            }
1492        }
1493    }
1494    match expr {
1495        Expression::New(_, i)
1496        | Expression::Parenthesis(_, i)
1497        | Expression::MemberAccess(_, i, _)
1498        | Expression::Delete(_, i) => collect_low_level_call_method_refs_expr(i, sink),
1499        Expression::FunctionCall(_, func, args) => {
1500            collect_low_level_call_method_refs_expr(func, sink);
1501            for a in args {
1502                collect_low_level_call_method_refs_expr(a, sink);
1503            }
1504        }
1505        Expression::FunctionCallBlock(_, call, block) => {
1506            collect_low_level_call_method_refs_expr(call, sink);
1507            collect_low_level_call_method_refs_stmt(block, sink);
1508        }
1509        Expression::NamedFunctionCall(_, func, args) => {
1510            collect_low_level_call_method_refs_expr(func, sink);
1511            for a in args {
1512                collect_low_level_call_method_refs_expr(&a.expr, sink);
1513            }
1514        }
1515        Expression::ArraySubscript(_, a, b) => {
1516            collect_low_level_call_method_refs_expr(a, sink);
1517            if let Some(e) = b {
1518                collect_low_level_call_method_refs_expr(e, sink);
1519            }
1520        }
1521        Expression::ConditionalOperator(_, c, a, b) => {
1522            collect_low_level_call_method_refs_expr(c, sink);
1523            collect_low_level_call_method_refs_expr(a, sink);
1524            collect_low_level_call_method_refs_expr(b, sink);
1525        }
1526        Expression::Assign(_, a, b) => {
1527            collect_low_level_call_method_refs_expr(a, sink);
1528            collect_low_level_call_method_refs_expr(b, sink);
1529        }
1530        Expression::ArrayLiteral(_, values) => {
1531            for v in values {
1532                collect_low_level_call_method_refs_expr(v, sink);
1533            }
1534        }
1535        _ => {}
1536    }
1537}
1538
1539/// Task #194 — peel the `abi.encodeWith{Selector,Signature}` /
1540/// `abi.encodeCall` wrapper of a low-level call payload to extract the
1541/// Solidity method name when it is statically resolvable.
1542fn extract_static_method_name_from_payload(expr: &Expression) -> Option<String> {
1543    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1544        extract_static_method_name_from_payload_inner(expr)
1545    })
1546}
1547
1548fn extract_static_method_name_from_payload_inner(expr: &Expression) -> Option<String> {
1549    match expr {
1550        Expression::Parenthesis(_, inner) => extract_static_method_name_from_payload(inner),
1551        Expression::FunctionCall(_, func, args) => {
1552            // `bytes(<inner>)` / `bytes4(<inner>)` / `string(<inner>)` casts
1553            // are transparent — recurse through them.
1554            if args.len() == 1 {
1555                if let Expression::Variable(id) = func.as_ref() {
1556                    if id.name == "bytes" || id.name == "string" {
1557                        return extract_static_method_name_from_payload(&args[0]);
1558                    }
1559                }
1560                if matches!(func.as_ref(), Expression::Type(_, _)) {
1561                    return extract_static_method_name_from_payload(&args[0]);
1562                }
1563            }
1564
1565            let Expression::MemberAccess(_, inner, member) = func.as_ref() else {
1566                return None;
1567            };
1568
1569            if !matches!(inner.as_ref(), Expression::Variable(id) if id.name == "abi") {
1570                return None;
1571            }
1572
1573            match member.name.as_str() {
1574                "encodeWithSignature" => {
1575                    let first = args.first()?;
1576                    let signature = extract_static_signature_string(first)?;
1577                    let name = signature
1578                        .split('(')
1579                        .next()
1580                        .unwrap_or(signature.as_str())
1581                        .trim()
1582                        .to_string();
1583                    if name.is_empty() {
1584                        None
1585                    } else {
1586                        Some(name)
1587                    }
1588                }
1589                "encodeWithSelector" => {
1590                    let first = args.first()?;
1591                    extract_static_selector_method_name(first)
1592                }
1593                "encodeCall" => {
1594                    // `abi.encodeCall(X.method, (…))` — member-access
1595                    // function reference resolves to the member name.
1596                    let first = args.first()?;
1597                    extract_static_encode_call_method_name(first)
1598                }
1599                _ => None,
1600            }
1601        }
1602        _ => None,
1603    }
1604}
1605
1606/// Task #194 — analogue of `resolve_selector_method_name` in
1607/// `ir/build/selectors.rs` that operates on raw `solang_parser::pt`
1608/// expressions (the analyse pass runs before the IR is built).
1609fn extract_static_selector_method_name(expr: &Expression) -> Option<String> {
1610    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1611        extract_static_selector_method_name_inner(expr)
1612    })
1613}
1614
1615fn extract_static_selector_method_name_inner(expr: &Expression) -> Option<String> {
1616    match expr {
1617        Expression::Parenthesis(_, inner) => extract_static_selector_method_name(inner),
1618        Expression::MemberAccess(_, inner, member) => {
1619            if member.name == "selector" {
1620                match inner.as_ref() {
1621                    Expression::MemberAccess(_, _, function_name) => {
1622                        let name = function_name.name.trim();
1623                        if name.is_empty() {
1624                            None
1625                        } else {
1626                            Some(name.to_string())
1627                        }
1628                    }
1629                    Expression::Variable(function_name) => {
1630                        let name = function_name.name.trim();
1631                        if name.is_empty() {
1632                            None
1633                        } else {
1634                            Some(name.to_string())
1635                        }
1636                    }
1637                    _ => None,
1638                }
1639            } else {
1640                None
1641            }
1642        }
1643        Expression::FunctionCall(_, func, args) => {
1644            if matches!(func.as_ref(), Expression::Type(_, _)) && args.len() == 1 {
1645                return extract_static_selector_method_name(&args[0]);
1646            }
1647            if let Expression::Variable(id) = func.as_ref() {
1648                if (id.name == "bytes" || id.name == "string") && args.len() == 1 {
1649                    return extract_static_selector_method_name(&args[0]);
1650                }
1651                if id.name == "keccak256" && args.len() == 1 {
1652                    let signature = extract_static_signature_string(&args[0])?;
1653                    let name = signature
1654                        .split('(')
1655                        .next()
1656                        .unwrap_or(signature.as_str())
1657                        .trim()
1658                        .to_string();
1659                    if name.is_empty() {
1660                        return None;
1661                    }
1662                    return Some(name);
1663                }
1664            }
1665            None
1666        }
1667        _ => None,
1668    }
1669}
1670
1671/// Task #194 — recognise the function reference argument of
1672/// `abi.encodeCall(funcRef, tuple)`. Accepts `Type.method`,
1673/// `instance.method`, or nested member-access chains and returns the
1674/// outermost member name.
1675fn extract_static_encode_call_method_name(expr: &Expression) -> Option<String> {
1676    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1677        extract_static_encode_call_method_name_inner(expr)
1678    })
1679}
1680
1681fn extract_static_encode_call_method_name_inner(expr: &Expression) -> Option<String> {
1682    match expr {
1683        Expression::Parenthesis(_, inner) => extract_static_encode_call_method_name(inner),
1684        Expression::MemberAccess(_, _inner, member) => {
1685            if member.name == "selector" {
1686                // `abi.encodeCall(X.method.selector, …)` — uncommon but we
1687                // can still recover the method name by looking one level up.
1688                if let Expression::MemberAccess(_, _, function_name) = _inner.as_ref() {
1689                    let name = function_name.name.trim();
1690                    if !name.is_empty() {
1691                        return Some(name.to_string());
1692                    }
1693                }
1694                return None;
1695            }
1696            let name = member.name.trim();
1697            if name.is_empty() {
1698                None
1699            } else {
1700                Some(name.to_string())
1701            }
1702        }
1703        _ => None,
1704    }
1705}
1706
1707/// Task #194 — compile-time constant string extraction. Peels `bytes(…)`
1708/// / `string(…)` casts and unwraps `Parenthesis` but stops at the first
1709/// non-literal (e.g. `constant`-stored strings are not read here because
1710/// the analyse pass doesn't have access to the lowering context yet).
1711fn extract_static_signature_string(expr: &Expression) -> Option<String> {
1712    stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
1713        extract_static_signature_string_inner(expr)
1714    })
1715}
1716
1717fn extract_static_signature_string_inner(expr: &Expression) -> Option<String> {
1718    match expr {
1719        Expression::Parenthesis(_, inner) => extract_static_signature_string(inner),
1720        Expression::StringLiteral(parts) => {
1721            let mut bytes = Vec::new();
1722            for part in parts {
1723                bytes.extend_from_slice(part.string.as_bytes());
1724            }
1725            Some(String::from_utf8_lossy(&bytes).to_string())
1726        }
1727        Expression::FunctionCall(_, func, args) if args.len() == 1 => match func.as_ref() {
1728            Expression::Type(_, _) => extract_static_signature_string(&args[0]),
1729            Expression::Variable(id) if id.name == "bytes" || id.name == "string" => {
1730                extract_static_signature_string(&args[0])
1731            }
1732            _ => None,
1733        },
1734        _ => None,
1735    }
1736}
1737
1738/// Task #206 — compute the DIRECT sibling-primary references a contract body
1739/// introduces. Used by the sibling-merge closure so multi-hop cross-contract
1740/// call chains pull every reachable primary into the root artifact's
1741/// self-dispatch table.
1742fn collect_direct_sibling_contract_refs(
1743    contract: &ContractIR,
1744    primary_names: &std::collections::HashSet<String>,
1745    interface_names: &std::collections::HashSet<String>,
1746    interface_impls: &std::collections::HashMap<String, Vec<String>>,
1747    primary_method_names: &std::collections::HashMap<
1748        String,
1749        std::collections::HashSet<String>,
1750    >,
1751) -> std::collections::HashSet<String> {
1752    let mut referenced: std::collections::HashSet<String> =
1753        std::collections::HashSet::new();
1754    let mut iface_refs: std::collections::HashSet<String> =
1755        std::collections::HashSet::new();
1756    let mut low_level_method_refs: std::collections::HashSet<String> =
1757        std::collections::HashSet::new();
1758
1759    for function in &contract.functions {
1760        if let Some(body) = function.body.as_ref() {
1761            collect_new_contract_refs(body, primary_names, &mut referenced);
1762            collect_interface_casts_stmt(body, interface_names, &mut iface_refs);
1763            collect_low_level_call_method_refs_stmt(body, &mut low_level_method_refs);
1764        }
1765        for p in function.parameters.iter().chain(function.returns.iter()) {
1766            if primary_names.contains(&p.ty) {
1767                referenced.insert(p.ty.clone());
1768            }
1769            if interface_names.contains(&p.ty) {
1770                iface_refs.insert(p.ty.clone());
1771            }
1772        }
1773    }
1774
1775    for state in &contract.state_variables {
1776        if primary_names.contains(&state.ty) {
1777            referenced.insert(state.ty.clone());
1778        }
1779        if interface_names.contains(&state.ty) {
1780            iface_refs.insert(state.ty.clone());
1781        }
1782        if let Some(init) = state.initializer.as_ref() {
1783            collect_new_refs_expr(init, primary_names, &mut referenced);
1784            collect_interface_casts_expr(init, interface_names, &mut iface_refs);
1785            collect_low_level_call_method_refs_expr(init, &mut low_level_method_refs);
1786        }
1787    }
1788
1789    for iface in &iface_refs {
1790        if let Some(impls) = interface_impls.get(iface) {
1791            for prim in impls {
1792                if prim != &contract.name {
1793                    referenced.insert(prim.clone());
1794                }
1795            }
1796        }
1797    }
1798
1799    if !low_level_method_refs.is_empty() {
1800        for (prim_name, prim_methods) in primary_method_names {
1801            if prim_name == &contract.name {
1802                continue;
1803            }
1804            if low_level_method_refs
1805                .iter()
1806                .any(|method| prim_methods.contains(method))
1807            {
1808                referenced.insert(prim_name.clone());
1809            }
1810        }
1811    }
1812
1813    referenced.remove(&contract.name);
1814    referenced
1815}
1816
1817/// Normalize a state-variable type string for the sibling-merge collision
1818/// check so that equivalent spellings compare equal: whitespace is dropped
1819/// and the bare `uint`/`int` aliases expand to their canonical 256-bit
1820/// forms (`uint256[3]` == `uint [3]` == `uint256 [ 3 ]`,
1821/// `mapping(address=>uint)` == `mapping(address => uint256)`).
1822fn normalize_state_type_for_merge(ty: &str) -> String {
1823    let mut out = String::with_capacity(ty.len());
1824    let mut word = String::new();
1825    let flush = |word: &mut String, out: &mut String| {
1826        if word.is_empty() {
1827            return;
1828        }
1829        match word.as_str() {
1830            "uint" => out.push_str("uint256"),
1831            "int" => out.push_str("int256"),
1832            other => out.push_str(other),
1833        }
1834        word.clear();
1835    };
1836    for ch in ty.chars() {
1837        if ch.is_alphanumeric() || ch == '_' || ch == '$' {
1838            word.push(ch);
1839        } else {
1840            flush(&mut word, &mut out);
1841            if !ch.is_whitespace() {
1842                out.push(ch);
1843            }
1844        }
1845    }
1846    flush(&mut word, &mut out);
1847    out
1848}