traverse_graph/
chains.rs

1//! Module for resolving types and analyzing chained calls in Solidity expressions.
2
3use crate::builtin;
4use crate::cg::{
5    CallGraph, CallGraphGeneratorContext, CallGraphGeneratorInput, NodeInfo, NodeType,
6};
7use crate::parser::get_node_text;
8use std::collections::VecDeque;
9use streaming_iterator::StreamingIterator;
10use tree_sitter::{Node as TsNode, Query, QueryCursor};
11use tracing::{trace, error};
12
13#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)]
14pub enum TypeError {
15    #[error("Query error: {0}")]
16    QueryError(String),
17
18    #[error("Missing child: {0}")]
19    MissingChild(String),
20
21    #[error("Failed to resolve type for expression '{expr}': {reason}")]
22    TypeResolutionFailed { expr: String, reason: String },
23
24    #[error("Failed to resolve target '{name}': {reason}")]
25    TargetResolutionFailed { name: String, reason: String },
26
27    #[error("Unsupported node kind: {0}")]
28    UnsupportedNodeKind(String),
29
30    #[error("Ambiguous implementation for interface {interface_name}.{method_name}, found implementations: {implementations:?}")]
31    AmbiguousInterfaceImplementation {
32        interface_name: String,
33        method_name: String,
34        implementations: Vec<String>,
35    },
36
37    #[error("Internal error: {0}")]
38    Internal(String),
39}
40
41impl From<tree_sitter::QueryError> for TypeError {
42    fn from(error: tree_sitter::QueryError) -> Self {
43        // Convert the tree-sitter error to a string representation
44        TypeError::QueryError(error.to_string())
45    }
46}
47
48// --- Data Structures ---
49
50/// Represents the resolved target of a specific call step.
51/// Stores names, allowing cg.rs to perform the final node ID lookup.
52#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
53pub enum ResolvedTarget {
54    /// Resolved to a specific function/modifier/constructor.
55    Function {
56        contract_name: Option<String>, // None for free functions
57        function_name: String,
58        node_type: NodeType, // Function, Constructor, or Modifier
59    },
60    /// Resolved to an interface method. Implementation might be unique, ambiguous, or external.
61    InterfaceMethod {
62        interface_name: String,
63        method_name: String,
64        /// If a single, concrete implementation was found, provide its details.
65        implementation: Option<Box<ResolvedTarget>>, // Box to avoid recursive type size issue
66    },
67    /// Represents a built-in function or property (e.g., .push, .balance).
68    BuiltIn { object_type: String, name: String },
69    /// Resolution failed or target is not callable (e.g., state variable access).
70    NotCallable { reason: String },
71    /// Target is external/unknown (e.g., call to an address).
72    External { address_expr: String },
73    /// Represents a type cast expression like `TypeName(...)`.
74    TypeCast { type_name: String },
75}
76
77/// Represents a single step in a potentially chained call sequence.
78// Added PartialEq, Eq, PartialOrd, Ord for sorting and deduplication
79#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
80pub struct ResolvedCallStep {
81    /// The byte offsets representing the span of this specific call expression (e.g., `.method(...)`).
82    pub call_expr_span: (usize, usize),
83    /// The byte offsets representing the span of the function/member being called (e.g., `method` in `obj.method()`).
84    pub function_span: (usize, usize),
85    /// The resolved target for this call step.
86    pub target: ResolvedTarget,
87    /// Text representation of the arguments passed in this call step.
88    pub arguments: Vec<String>,
89    /// The Solidity type name resolved for the *result* of this call step.
90    /// This becomes the object type for the *next* step in the chain.
91    /// None if the call returns void or resolution failed.
92    pub result_type: Option<String>,
93    /// The Solidity type name resolved for the *object* being called upon in this step.
94    /// None for simple function calls or `new` expressions.
95    pub object_type: Option<String>,
96    /// The source text of the object instance being called upon.
97    /// e.g., "myVar" in "myVar.method()", or "getStruct().field" in "getStruct().field.method()".
98    /// None for simple function calls or `new` expressions.
99    pub object_instance_text: Option<String>,
100    /// The start byte of the original expression that initiated this chain analysis.
101    /// Used for sorting steps correctly.
102    pub originating_span_start: usize,
103    /// If the target is BuiltIn (like push/pop), this holds the name of the base variable identifier
104    /// being acted upon (e.g., "allPairs" in "allPairs.push()").
105    pub base_object_identifier_for_builtin: Option<String>,
106}
107
108// --- Main Analysis Function ---
109
110/// Analyzes a potentially chained call expression node to resolve types and targets.
111///
112/// This function attempts to decompose expressions like `a.b(c).d(e)` into a sequence of resolved steps.
113///
114/// Args:
115/// * `start_node`: The tree-sitter node representing the beginning of the expression
116///   (e.g., an identifier, a type cast, a `new` expression, or the full chained call).
117/// * `caller_node_id`: The ID of the node (in the eventual graph) containing this call.
118/// * `caller_contract_name_opt`: The name of the contract containing the caller, if any.
119/// * `ctx`: The context containing information about contracts, interfaces, state vars, etc.
120/// * `graph`: The current call graph (needed for node lookups during resolution).
121/// * `source`: The original source code string.
122/// * `solidity_lang`: The tree-sitter language definition.
123///
124/// Returns:
125/// A vector of `ResolvedCallStep` structs, one for each call in the chain,
126/// or a `TypeError` if analysis fails at any point.
127pub(crate) fn analyze_chained_call<'a>(
128    start_node: TsNode<'a>,
129    caller_node_id: usize,
130    caller_contract_name_opt: &'a Option<String>,
131    ctx: &CallGraphGeneratorContext,
132    graph: &'a CallGraph, // Pass graph for lookups
133    source: &'a str,
134    solidity_lang: &'a tree_sitter::Language,
135    input: &'a CallGraphGeneratorInput,
136    original_start_node_for_err_reporting: Option<TsNode<'a>>, // Keep for error reporting if needed
137    originating_span_start: usize,                             // Pass the originating span start
138) -> std::result::Result<Vec<ResolvedCallStep>, TypeError> {
139    // If original_start_node is None, this is the top-level call, so use start_node
140    let original_start_node_for_err_reporting =
141        original_start_node_for_err_reporting.unwrap_or(start_node); // Keep if needed for errors
142                                                                     // Use full path to Result
143    let mut steps = Vec::new();
144
145    trace!(
146        node_kind = start_node.kind(),
147        node_text = get_node_text(&start_node, source).trim(),
148        "Starting chained analysis"
149    );
150
151    match start_node.kind() {
152        // --- Base Cases (Start of a chain or simple expression) ---
153        "identifier" | "primitive_type" | "string_literal" | "number_literal"
154        | "boolean_literal" | "hex_literal" | "address_literal" => {
155            // This is not a call itself, but the start of a potential chain. Resolve its type.
156            // Resolve type, but no steps generated from here directly.
157            // The caller using this identifier/literal will handle type resolution.
158            let _resolved_type = resolve_expression_type_v2(
159                start_node,
160                caller_node_id,
161                caller_contract_name_opt,
162                ctx,
163                graph,
164                source,
165                solidity_lang,
166                input,
167            )?;
168            trace!(
169                node_kind = start_node.kind(),
170                ?_resolved_type,
171                "Base case node processed"
172            );
173            // If it's just an identifier/literal, there are no call steps *from* it directly.
174            // The chain must continue via member access or call expression wrapping it.
175        }
176
177        "new_expression" => {
178            // Handle `new Contract(...)` as the start of a potential chain
179            let type_name_node = start_node.named_child(0).ok_or_else(|| {
180                TypeError::MissingChild(format!(
181                    "new_expression missing type_name (named_child(0) failed for node: {})",
182                    get_node_text(&start_node, source)
183                ))
184            })?;
185            let contract_name = get_node_text(&type_name_node, source).to_string(); // Simplified
186
187            // The "call" is to the constructor
188            let target = ResolvedTarget::Function {
189                contract_name: Some(contract_name.clone()),
190                function_name: contract_name.clone(), // Constructor name is contract name
191                node_type: NodeType::Constructor,
192            };
193
194            // Arguments for `new` are usually in the parent `call_expression`.
195            // We need the parent node to extract them correctly.
196            // For now, assume the caller (likely the `call_expression` handler below)
197            // provides the arguments if `new` is wrapped. If `new` is standalone, args are empty.
198            // Let's refine argument extraction later if needed.
199            let arguments = if let Some(parent) = start_node.parent() {
200                if parent.kind() == "call_expression" {
201                    extract_arguments_v2(parent, source)
202                } else {
203                    vec![] // Standalone new expression
204                }
205            } else {
206                vec![]
207            };
208
209            let step = ResolvedCallStep {
210                call_expr_span: (start_node.start_byte(), start_node.end_byte()), // Use start/end_byte
211                function_span: (
212                    type_name_node.start_byte(), // Use start/end_byte
213                    type_name_node.end_byte(),   // Use start/end_byte
214                ), // Span of the type name
215                target,
216                arguments,
217                result_type: Some(contract_name.clone()), // Result type is the contract itself
218                object_type: None,                        // No object for `new`
219                object_instance_text: None,               // No object instance for `new`
220                originating_span_start,                   // Populate the new field
221                base_object_identifier_for_builtin: None, // Added: No base object for 'new'
222            };
223            steps.push(step);
224            trace!(
225                result_type = ?steps.last().and_then(|s| s.result_type.as_ref()),
226                "New expression step added"
227            );
228        }
229
230        // --- Recursive / Chaining Cases ---
231        "call_expression" => {
232            let function_node = start_node // Use start_node
233                .child_by_field_name("function")
234                .ok_or_else(|| {
235                    TypeError::MissingChild("call_expression missing function".to_string())
236                })?;
237            // Get argument expression nodes
238            let argument_nodes = find_argument_expression_nodes(start_node); // Use start_node
239
240            trace!(
241                        "Handling call_expression. Function node kind: '{}', text: '{}', Num args: {}",
242                        function_node.kind(),
243                        get_node_text(&function_node, source).trim(),
244                        argument_nodes.len()
245                    );
246
247            // --- 1. Recursively analyze arguments FIRST ---
248            let mut argument_steps = Vec::new();
249            let mut argument_texts = Vec::with_capacity(argument_nodes.len());
250            for arg_node in argument_nodes {
251                trace!(
252                    "  Analyzing argument node kind: '{}', text: '{}'",
253                    arg_node.kind(),
254                    get_node_text(&arg_node, source).trim()
255                );
256                // Recursively call analyze_chained_call for each argument
257                let inner_steps = analyze_chained_call(
258                    arg_node,
259                    caller_node_id,
260                    caller_contract_name_opt,
261                    ctx,
262                    graph,
263                    source,
264                    solidity_lang,
265                    input,
266                    Some(original_start_node_for_err_reporting),
267                    originating_span_start, // Pass down the same originating span start
268                )?;
269
270                argument_steps.extend(inner_steps);
271                // Store the original text of the argument for the outer call step
272                argument_texts.push(get_node_text(&arg_node, source).to_string());
273            }
274            trace!(
275                "  Finished analyzing arguments. Collected {} inner steps.",
276                argument_texts.len()
277            );
278
279            // --- 2. Process the outer call itself ---
280            // let mut object_steps = Vec::new(); // Initialized later if needed
281            // let mut outer_object_type: Option<String> = None; // Initialized later if needed
282
283            match function_node.kind() {
284                // Case A: Simple call `foo()` or type cast `Type(arg)`
285                "expression"
286                    if function_node
287                        .child(0)
288                        .map_or(false, |n| n.kind() == "identifier") =>
289                {
290                    let id_node = function_node.child(0).unwrap();
291                    let name = get_node_text(&id_node, source).to_string();
292
293                    // Check for Type Cast `TypeName(...)` - these don't generate a call step themselves
294                    if name.chars().next().map_or(false, |c| c.is_uppercase())
295                        && (ctx.all_contracts.contains_key(&name)
296                            || ctx.all_interfaces.contains_key(&name)
297                            || ctx.all_libraries.contains_key(&name))
298                    // Check libraries too
299                    {
300                        trace!(
301                                    "  Outer call is Type Cast/Constructor '{}'. No outer step generated.", name
302                                );
303                        // The type resolution happens via the recursive call on the argument(s).
304                        // The result type for the *overall* expression is the type name itself.
305                        // However, analyze_chained_call returns steps, not just the final type.
306                        // If this cast is the *outermost* node analyzed, we might need
307                        // a way to signal the final type without a step.
308                        // For now, if arguments generated steps, those are returned.
309                        // If no arguments or simple args, steps might be empty.
310                        // This case needs careful handling depending on how the result is used.
311                        // Let's assume for now that if it's just a cast, the caller handles it.
312                        // If it's `new Type()`, it's handled by the `new_expression` case.
313                        // If it's `Type(arg).method()`, the `member_expression` handler deals with it.
314                        // So, if we reach here, it's likely just `Type(arg)` used as a value.
315                        // We return the steps generated by analyzing `arg`.
316                    } else {
317                        // Simple function call `foo()`
318                        trace!(
319                            "  Outer call is Simple Function Call '{}'",
320                            name
321                        );
322                        let target =
323                            resolve_simple_call_v2(&name, caller_contract_name_opt, graph, ctx)?;
324                        let result_type = resolve_call_return_type(
325                            &target,
326                            ctx,
327                            graph,
328                            source,
329                            solidity_lang,
330                            input,
331                        )?;
332
333                        let outer_step = ResolvedCallStep {
334                            call_expr_span: (start_node.start_byte(), start_node.end_byte()),
335                            function_span: (id_node.start_byte(), id_node.end_byte()),
336                            target,
337                            arguments: argument_texts, // Use collected texts
338                            result_type: result_type.clone(),
339                            object_type: None, // No object for simple calls
340                            object_instance_text: None, // No object instance for simple calls
341                            originating_span_start, // Populate the new field
342                            base_object_identifier_for_builtin: None, // Added: No base object for simple calls
343                        };
344                        // Combine steps: argument_steps first, then the outer_step
345                        steps = argument_steps; // Start with argument steps
346                        steps.push(outer_step); // Add the simple call step
347                        trace!(
348                            "  Simple call step added. Result type: {:?}. Total steps for this branch: {}",
349                            result_type, steps.len()
350                        );
351                    }
352                }
353
354                // Case B: Member call `obj.method()` or `expr.method()`
355                "expression"
356                    if function_node
357                        .child(0)
358                        .map_or(false, |n| n.kind() == "member_expression") =>
359                {
360                    let member_expr_node = function_node.child(0).unwrap();
361                    let object_node =
362                        member_expr_node
363                            .child_by_field_name("object")
364                            .ok_or_else(|| {
365                                TypeError::MissingChild(
366                                    "member_expression missing object".to_string(),
367                                )
368                            })?;
369                    let property_node = member_expr_node
370                        .child_by_field_name("property")
371                        .ok_or_else(|| {
372                            TypeError::MissingChild(
373                                "member_expression missing property".to_string(),
374                            )
375                        })?;
376
377                    if property_node.kind() != "identifier" {
378                        return Err(TypeError::UnsupportedNodeKind(format!(
379                            "Member expression property kind '{}' not supported",
380                            property_node.kind()
381                        )));
382                    }
383                    let property_name = get_node_text(&property_node, source).to_string();
384                    trace!(
385                        "  Outer call is Member Call '.{}'",
386                        property_name
387                    );
388                    // Recursively analyze the object part first
389                    let object_steps = analyze_chained_call(
390                        // Assign to the outer object_steps
391                        object_node,
392                        caller_node_id,
393                        caller_contract_name_opt,
394                        ctx,
395                        graph,
396                        source,
397                        solidity_lang,
398                        input,
399                        Some(original_start_node_for_err_reporting),
400                        originating_span_start, // Pass down the same originating span start
401                    )?;
402                    // --- Try to capture the base identifier if the object is simple ---
403                    let base_identifier_name = if object_node.kind() == "identifier" {
404                        Some(get_node_text(&object_node, source).to_string())
405                    } else {
406                        // TODO: Handle more complex base objects if needed, e.g., struct members `myStruct.arr.push()`
407                        // For now, only capture direct identifiers like `allPairs`.
408                        None
409                    };
410
411                    // Determine the type of the object for *this* call
412                    let outer_object_type = if let Some(last_object_step) = object_steps.last() {
413                        last_object_step.result_type.clone()
414                    } else {
415                        // If object analysis yielded no steps (e.g., simple identifier)
416                        resolve_expression_type_v2(
417                            object_node,
418                            caller_node_id,
419                            caller_contract_name_opt,
420                            ctx,
421                            graph,
422                            source,
423                            solidity_lang,
424                            input,
425                        )?
426                    };
427                    trace!(
428                        "    Resolved outer_object_type for member call: {:?}",
429                        outer_object_type
430                    );
431
432                    if let Some(ref obj_type) = outer_object_type {
433                        trace!(
434                            "    Object type for '.{}' call resolved to: '{}'",
435                            property_name, obj_type
436                        );
437                        trace!("    Calling resolve_member_or_library_call_v2 with obj_type='{}', property='{}'", obj_type, property_name);
438                        let target = resolve_member_or_library_call_v2(
439                            obj_type,
440                            &property_name,
441                            caller_contract_name_opt,
442                            graph,
443                            ctx,
444                            source,
445                            (member_expr_node.start_byte(), member_expr_node.end_byte()),
446                        )?;
447                        let result_type = resolve_call_return_type(
448                            &target,
449                            ctx,
450                            graph,
451                            source,
452                            solidity_lang,
453                            input,
454                        )?;
455
456                        let current_object_instance_text =
457                            Some(get_node_text(&object_node, source).to_string());
458
459                        let outer_step = ResolvedCallStep {
460                            call_expr_span: (start_node.start_byte(), start_node.end_byte()),
461                            function_span: (property_node.start_byte(), property_node.end_byte()),
462                            target: target.clone(), // Clone target here to allow reuse below
463                            arguments: argument_texts, // Use collected texts
464                            result_type: result_type.clone(),
465                            object_type: outer_object_type.clone(),
466                            object_instance_text: current_object_instance_text, // Populate the instance text
467                            originating_span_start, // Populate the new field
468                            base_object_identifier_for_builtin: if matches!(
469                                target,
470                                ResolvedTarget::BuiltIn { .. }
471                            ) {
472                                base_identifier_name // Use the captured name
473                            } else {
474                                None
475                            },
476                        };
477                        // Combine steps: object_steps first, then argument_steps, then the outer_step
478                        steps = object_steps; // Start with object steps
479                        steps.extend(argument_steps); // Add argument steps
480                        steps.push(outer_step); // Add the final member call step
481                        trace!(
482                            "    Member call step added. Result type: {:?}. Total steps for this branch: {}",
483                            result_type, steps.len()
484                        );
485                    } else {
486                        trace!("    Failed to resolve object type for member call '.{}'", property_name);
487                        return Err(TypeError::TypeResolutionFailed {
488                            expr: get_node_text(&object_node, source).to_string(),
489                            reason: "Could not determine type for member call.".to_string(),
490                        });
491                    }
492                }
493
494                // Case C: Constructor call `new Contract(...)` - Handled by `new_expression` case now?
495                // Let's double-check the AST structure. `new Contract()` is often wrapped in `call_expression`.
496                // The `new_expression` case above handles the `new` node itself.
497                // If the `call_expression` handler finds a `new_expression` inside, it should delegate.
498                "expression"
499                    if function_node
500                        .child(0)
501                        .map_or(false, |n| n.kind() == "new_expression") =>
502                {
503                    // Recursively analyze the new_expression itself to get its step(s)
504                    let _object_steps: Vec<ResolvedCallStep> = Vec::new(); // To satisfy structure, though new_steps is what's used
505                    let _outer_object_type: Option<String>; // To satisfy structure
506
507                    let new_expression_node = function_node.child(0).unwrap();
508                    let mut new_constructor_steps = analyze_chained_call(
509                        new_expression_node, // The new_expression node
510                        caller_node_id,
511                        caller_contract_name_opt,
512                        ctx,
513                        graph,
514                        source,
515                        solidity_lang,
516                        input,
517                        Some(original_start_node_for_err_reporting),
518                        originating_span_start,
519                    )?;
520
521                    // If the new_expression generated a step (it should for a constructor),
522                    // update its arguments with those from the outer call_expression.
523                    if let Some(constructor_step) = new_constructor_steps.get_mut(0) {
524                        // argument_texts were collected earlier in the call_expression handler
525                        constructor_step.arguments = argument_texts.clone();
526                    }
527
528                    // Combine steps: argument_steps first, then the (potentially modified) new_constructor_steps
529                    steps = argument_steps; // Start with argument steps
530                    steps.extend(new_constructor_steps); // Add steps from analyzing the 'new' expression
531                    trace!(
532                        "  'new' expression wrapped in call processed. Total steps for this branch: {}",
533                        steps.len()
534                    );
535                }
536                _ => {
537                    return Err(TypeError::UnsupportedNodeKind(format!(
538                        "Unsupported function node kind in call_expression: '{}'",
539                        function_node.kind()
540                    )));
541                }
542            }
543        }
544
545        "member_expression" => {
546            // Handle cases like `a.b` where `b` is accessed but not called.
547            // This sets the stage for a potential *next* call in the chain.
548            let object_node = start_node // Use start_node
549                .child_by_field_name("object")
550                .ok_or_else(|| {
551                    TypeError::MissingChild("member_expression missing object".to_string())
552                })?;
553            let property_node = start_node // Use start_node
554                .child_by_field_name("property")
555                .ok_or_else(|| {
556                    TypeError::MissingChild("member_expression missing property".to_string())
557                })?;
558
559            if property_node.kind() != "identifier" {
560                return Err(TypeError::UnsupportedNodeKind(format!(
561                    "Member expression property kind '{}' not supported",
562                    property_node.kind()
563                )));
564            }
565            let property_name = get_node_text(&property_node, source).to_string();
566
567            trace!(
568                "Handling member_expression '.{}' (not a call)",
569                property_name
570            );
571
572            // Recursively analyze the object part first
573            let object_steps = analyze_chained_call(
574                object_node,
575                caller_node_id,
576                caller_contract_name_opt,
577                ctx,
578                graph,
579                source,
580                solidity_lang,
581                input,
582                Some(original_start_node_for_err_reporting), // Use the correct parameter name
583                originating_span_start, // Pass down the same originating span start
584            )?;
585            steps.extend(object_steps); // Prepend object steps
586
587            // This member access itself doesn't generate a call step.
588            // We just needed to ensure the object part was analyzed.
589            // The type resolution for the *next* step will handle this property.
590            trace!(
591                "  Member access processed. {} inner steps added.",
592                steps.len()
593            );
594        }
595
596        "expression" => {
597            // Delegate analysis to the first child of the expression node
598            if let Some(child_node) = start_node.child(0) {
599                // Use start_node
600                trace!(
601                    "Delegating 'expression' analysis to child '{}'.",
602                    child_node.kind()
603                );
604                // Recursively call analyze_chained_call on the child.
605                // The result of this recursive call *is* the result for the expression node.
606                return analyze_chained_call(
607                    // Use return here
608                    child_node,
609                    caller_node_id,
610                    caller_contract_name_opt,
611                    ctx,
612                    graph,
613                    source,
614                    solidity_lang,
615                    input,
616                    Some(original_start_node_for_err_reporting),
617                    originating_span_start, // Pass down the same originating span start
618                );
619            } else {
620                trace!("'expression' node has no children.");
621                // Fall through to return the current (likely empty) steps vector.
622            }
623        }
624        "binary_expression" => {
625            // Recursively analyze left and right operands to collect steps within them
626            let left_node = start_node.child_by_field_name("left").ok_or_else(|| {
627                TypeError::MissingChild("binary_expression missing left operand".to_string())
628            })?;
629            let right_node = start_node.child_by_field_name("right").ok_or_else(|| {
630                TypeError::MissingChild("binary_expression missing right operand".to_string())
631            })?;
632
633            trace!(
634                "  Binary expression: Analyzing left operand kind: '{}'",
635                left_node.kind()
636            );
637            let left_steps = analyze_chained_call(
638                left_node,
639                caller_node_id,
640                caller_contract_name_opt,
641                ctx,
642                graph,
643                source,
644                solidity_lang,
645                input,
646                Some(original_start_node_for_err_reporting), // Use the correct parameter name
647                originating_span_start, // Pass down the same originating span start
648            )?;
649            steps.extend(left_steps); // Add steps from left operand
650
651            trace!(
652                "  Binary expression: Analyzing right operand kind: '{}'",
653                right_node.kind()
654            );
655            let right_steps = analyze_chained_call(
656                right_node,
657                caller_node_id,
658                caller_contract_name_opt,
659                ctx,
660                graph,
661                source,
662                solidity_lang,
663                input,
664                Some(original_start_node_for_err_reporting), // Use the correct parameter name
665                originating_span_start, // Pass down the same originating span start
666            )?;
667            steps.extend(right_steps); // Add steps from right operand
668
669            trace!(
670                "  Binary expression processed. Total steps for this branch now: {}",
671                steps.len()
672            );
673            // The type resolution for the binary expression itself happens in resolve_expression_type_v2
674        }
675        // Default case for other unhandled node kinds
676        _ => {
677            // If the start node is an unhandled kind, try resolving its type.
678            // This might be the start of a chain like `(a + b).call()`.
679            // No steps generated directly from these.
680            let _resolved_type = resolve_expression_type_v2(
681                // Mark unused
682                start_node, // Use start_node
683                caller_node_id,
684                caller_contract_name_opt,
685                ctx,
686                graph,
687                source,
688                solidity_lang,
689                input,
690            )?;
691            // Only log if it's truly unhandled, not common cases like type casts or binary ops
692            if !matches!(
693                start_node.kind(),
694                "type_cast_expression"
695                    | "binary_expression"
696                    | "unary_expression"
697                    | "parenthesized_expression"
698            ) {
699                trace!(
700                            "Potentially unhandled start node kind '{}', resolved type: {:?}. No steps generated.",
701                            start_node.kind(), // Use start_node
702                            _resolved_type
703                        );
704            } else {
705                trace!(
706                            "Handled base node kind '{}', resolved type: {:?}. No steps generated.",
707                            start_node.kind(), // Use start_node
708                            _resolved_type
709                        );
710            }
711        }
712    }
713    // --- Order Preservation ---
714    // The order is now determined by the sequence of analysis:
715    // object -> arguments -> outer call.
716    // Deduplication might need to be revisited if duplicate steps cause issues.
717
718    trace!(
719        "Analysis finished. Total steps generated: {}",
720        steps.len()
721    );
722    Ok(steps)
723}
724
725// --- Helper Functions ---
726
727/// Finds the direct child expression nodes corresponding to arguments in a call.
728fn find_argument_expression_nodes<'a>(call_expr_node: TsNode<'a>) -> Vec<TsNode<'a>> {
729    let mut arg_nodes = Vec::new();
730    let mut cursor = call_expr_node.walk();
731    for child in call_expr_node.children(&mut cursor) {
732        // Look for 'call_argument' nodes directly under the call_expression
733        if child.kind() == "call_argument" {
734            if let Some(expr_node) = child.child(0) {
735                // Assuming expression is the first child
736                arg_nodes.push(expr_node);
737            }
738        }
739        // Also handle the case where arguments are wrapped in an 'arguments' node
740        else if child.kind() == "arguments" {
741            let mut arg_cursor = child.walk();
742            for arg_child in child.children(&mut arg_cursor) {
743                if arg_child.kind() == "call_argument" {
744                    if let Some(expr_node) = arg_child.child(0) {
745                        arg_nodes.push(expr_node);
746                    }
747                }
748            }
749        }
750    }
751    arg_nodes
752}
753
754/// Resolves the Solidity type name for a given expression node (V2).
755/// Inspired by cg::resolve_expression_type but adapted for this module.
756fn resolve_expression_type_v2<'a>(
757    expr_node: TsNode<'a>,
758    caller_node_id: usize,
759    caller_contract_name_opt: &'a Option<String>,
760    ctx: &CallGraphGeneratorContext,
761    graph: &'a CallGraph,
762    source: &'a str,
763    solidity_lang: &'a tree_sitter::Language,
764    input: &'a CallGraphGeneratorInput, // Pass input for potential use in future
765) -> std::result::Result<Option<String>, TypeError> {
766    // Use full path to Result
767    let expr_text = get_node_text(&expr_node, source).trim().to_string();
768    trace!(
769        "[Resolve Type V2] Resolving type for node kind: '{}', text: '{}'",
770        expr_node.kind(),
771        expr_text
772    );
773
774    match expr_node.kind() {
775        // --- Base Cases ---
776        "identifier" => {
777            let name = expr_text;
778            if name == "Math" {
779                trace!(
780                    "[Resolve Type V2] Encountered identifier 'Math'. CallerScope='{:?}'",
781                    caller_contract_name_opt
782                );
783            }
784            // 1. Check state variables in current contract scope
785            if let Some(contract_name) = caller_contract_name_opt {
786                if let Some(type_name) = ctx
787                    .state_var_types
788                    .get(&(contract_name.clone(), name.clone()))
789                {
790                    trace!(
791                        "[Resolve Type V2]   Identifier '{}' resolved to state var type '{}'",
792                        name, type_name
793                    );
794                    return Ok(Some(type_name.clone()));
795                }
796            }
797            // TODO: Check local variables/parameters within the caller_node_id's definition_ts_node
798            // This requires parsing the function parameters and local variable declarations.
799            // --- Start: Check local variables ---
800            // Ensure we log attempt even if it fails to find node info later
801            if let Some(caller_node_graph_info) = graph.nodes.get(caller_node_id) {
802                let current_func_name = &caller_node_graph_info.name;
803                let current_contract_name = caller_node_graph_info
804                    .contract_name
805                    .as_deref()
806                    .unwrap_or("Global");
807
808                trace!("[Resolve Type V2 - LocalVar] Attempting to resolve identifier '{}' as local variable in function '{}.{}' (Caller Node ID: {}, Expr Node Span: {:?})", name, current_contract_name, current_func_name, caller_node_id, (expr_node.start_byte(), expr_node.end_byte()));
809
810                if let Some((_, caller_node_info_for_span, _)) =
811                    ctx // Renamed for clarity
812                        .definition_nodes_info
813                        .iter()
814                        .find(|(id, _, _)| *id == caller_node_id)
815                {
816                    if let Some(_definition_ts_node) =
817                        input.tree.root_node().descendant_for_byte_range(
818                            caller_node_info_for_span.span.0,
819                            caller_node_info_for_span.span.1,
820                        )
821                    {
822                        // --- Start: Check local variables (revised with upward search and correct boundary) ---
823                        let local_var_query_str = r#"
824                            (variable_declaration_statement
825                              (variable_declaration
826                                type: (_) @local_var_type
827                                name: (identifier) @local_var_name
828                              )
829                            ) @local_var_decl_stmt
830                        "#;
831                        let local_var_query =
832                            match Query::new(&input.solidity_lang, local_var_query_str) {
833                                Ok(q) => q,
834                                Err(e) => {
835                                    return Err(TypeError::QueryError(format!(
836                                        "Failed to create local var query: {}",
837                                        e
838                                    )))
839                                }
840                            };
841
842                        // Determine the enclosing true function TsNode for boundary
843                        let mut enclosing_true_function_ts_node: Option<TsNode> = None;
844                        let mut temp_ancestor_node_opt = Some(expr_node);
845                        while let Some(temp_ancestor_node) = temp_ancestor_node_opt {
846                            match temp_ancestor_node.kind() {
847                                "function_definition"
848                                | "modifier_definition"
849                                | "constructor_definition" => {
850                                    enclosing_true_function_ts_node = Some(temp_ancestor_node);
851                                    break;
852                                }
853                                _ => temp_ancestor_node_opt = temp_ancestor_node.parent(),
854                            }
855                        }
856
857                        if let Some(true_function_boundary_node) = enclosing_true_function_ts_node {
858                            trace!("[Resolve Type V2 - LocalVar Upward] Boundary for search: True function kind='{}', span=({},{})", true_function_boundary_node.kind(), true_function_boundary_node.start_byte(), true_function_boundary_node.end_byte());
859
860                            let mut current_scope_node_opt = expr_node.parent();
861                            trace!("[Resolve Type V2 - LocalVar Upward] Starting upward search for identifier '{}' (usage at byte {}), from parent kind: {:?}", name, expr_node.start_byte(), current_scope_node_opt.map(|n| n.kind()));
862
863                            while let Some(current_scope_node) = current_scope_node_opt {
864                                trace!("[Resolve Type V2 - LocalVar Upward]   Searching in scope: kind='{}', id={}, span=({},{})", current_scope_node.kind(), current_scope_node.id(), current_scope_node.start_byte(), current_scope_node.end_byte());
865
866                                let mut cursor = QueryCursor::new();
867                                let source_bytes = input.source.as_bytes();
868                                let mut matches = cursor.matches(
869                                    &local_var_query,
870                                    current_scope_node,
871                                    |n: TsNode| std::iter::once(&source_bytes[n.byte_range()]),
872                                );
873
874                                while let Some(match_) = matches.next() {
875                                    let mut var_name_opt: Option<String> = None;
876                                    let mut type_node_opt: Option<TsNode> = None;
877                                    let mut decl_stmt_node_opt: Option<TsNode> = None;
878
879                                    for capture in match_.captures {
880                                        let capture_name = &local_var_query.capture_names()
881                                            [capture.index as usize];
882                                        match *capture_name {
883                                            "local_var_name" => {
884                                                var_name_opt = Some(
885                                                    get_node_text(&capture.node, &input.source)
886                                                        .to_string(),
887                                                )
888                                            }
889                                            "local_var_type" => type_node_opt = Some(capture.node),
890                                            "local_var_decl_stmt" => {
891                                                decl_stmt_node_opt = Some(capture.node)
892                                            }
893                                            _ => {}
894                                        }
895                                    }
896
897                                    if let (
898                                        Some(var_name_found),
899                                        Some(type_node_found),
900                                        Some(decl_stmt_node),
901                                    ) = (var_name_opt, type_node_opt, decl_stmt_node_opt)
902                                    {
903                                        // If the query found it within current_scope_node, it's in scope.
904                                        // The main conditions are matching name and declaration appearing before usage.
905                                        if var_name_found == name
906                                            && decl_stmt_node.end_byte() <= expr_node.start_byte()
907                                        {
908                                            let type_name_found =
909                                                get_node_text(&type_node_found, &input.source)
910                                                    .to_string();
911                                            trace!("[Resolve Type V2 - LocalVar Upward]     MATCH! In scope '{}' (ID: {}), found decl for '{}' (type '{}') ending at byte {}. Usage at byte {}.", current_scope_node.kind(), current_scope_node.id(), var_name_found, type_name_found, decl_stmt_node.end_byte(), expr_node.start_byte());
912                                            return Ok(Some(type_name_found));
913                                        } else if var_name_found == name {
914                                            // This condition means name matched, but decl_stmt_node.end_byte() > expr_node.start_byte()
915                                            // (i.e., usage is before or within the declaration span, or at least not strictly after)
916                                            trace!("[Resolve Type V2 - LocalVar Upward]     Found decl for '{}' in scope '{}' (ID: {}), but usage (byte {}) is not strictly after declaration end (byte {}). Decl span: ({},{}).", name, current_scope_node.kind(), current_scope_node.id(), expr_node.start_byte(), decl_stmt_node.end_byte(), decl_stmt_node.start_byte(), decl_stmt_node.end_byte());
917                                        }
918                                        // Removed the 'else' block that logged "Skipping decl ... because its parent ... is not current_scope_node"
919                                        // as that parent check was removed.
920                                    }
921                                }
922
923                                if current_scope_node.id() == true_function_boundary_node.id() {
924                                    trace!("[Resolve Type V2 - LocalVar Upward]   Reached true function boundary ('{}'). Stopping upward search for '{}'.", true_function_boundary_node.kind(), name);
925                                    // current_scope_node_opt = None; // This assignment's value is reported as never read if loop breaks
926                                    break; // Stop inner match loop for this scope, will also stop outer while via current_scope_node_opt becoming None effectively
927                                }
928
929                                current_scope_node_opt = current_scope_node.parent();
930                                if current_scope_node_opt.is_none() {
931                                    trace!("[Resolve Type V2 - LocalVar Upward]   Reached tree root. Stopping upward search for '{}'.", name);
932                                    break;
933                                }
934                            } // End while current_scope_node_opt
935                        } else {
936                            trace!("[Resolve Type V2 - LocalVar Upward] Could not determine true function boundary for identifier '{}'. Local variable search might be incomplete.", name);
937                        }
938                        trace!("[Resolve Type V2 - LocalVar Upward] Finished upward search for '{}'. Not found as local variable in accessible scope.", name);
939                        // --- End: Check local variables ---
940                    } else {
941                        // This 'else' corresponds to: if let Some(_definition_ts_node) = input.tree.root_node()...
942                        trace!("[Resolve Type V2 - LocalVar]   Could not find definition TsNode for Caller Node ID: {}", caller_node_id);
943                    }
944                } else {
945                    // This 'else' corresponds to: if let Some((_, caller_node_info_for_span, _)) = ctx.definition_nodes_info...
946                    trace!("[Resolve Type V2 - LocalVar]   Could not find NodeInfo for Caller Node ID: {} in definition_nodes_info", caller_node_id);
947                }
948            } else {
949                // This 'else' corresponds to: if let Some(caller_node_graph_info) = graph.nodes.get(caller_node_id)
950                trace!("[Resolve Type V2 - LocalVar]   Could not find graph node info for Caller Node ID: {} (e.g. for function name/contract context)", caller_node_id);
951            }
952            // 2. Check if it's a known contract, library, or interface name (these are types)
953            let is_contract = ctx.all_contracts.contains_key(&name);
954            let is_library = ctx.all_libraries.contains_key(&name);
955            let is_interface = ctx.all_interfaces.contains_key(&name);
956            trace!("[Resolve Type V2]   Identifier '{}': is_contract={}, is_library={}, is_interface={}", name, is_contract, is_library, is_interface); // DEBUG LOG
957            if is_contract || is_library || is_interface {
958                trace!("[Resolve Type V2]   Identifier '{}' resolved to type (contract/library/interface) '{}'", name, name);
959                return Ok(Some(name));
960            }
961            // 3. Check if it's the special 'this' keyword
962            if name == "this" {
963                if let Some(contract_name) = caller_contract_name_opt {
964                    trace!(
965                        "[Resolve Type V2]   Identifier 'this' resolved to contract type '{}'",
966                        contract_name
967                    );
968                    return Ok(Some(contract_name.clone()));
969                } else {
970                    trace!("[Resolve Type V2]   Identifier 'this' used outside contract scope.");
971                    return Ok(None); // 'this' outside contract is invalid
972                }
973            }
974            // 4. Check if it's the special 'super' keyword
975            // TODO: Handle 'super' keyword resolution (requires inheritance info)
976
977            // Check if identifier resolves to a function in scope/inheritance ---
978            // This handles cases like `totalSupply.mul()` where `totalSupply` is a function call.
979            // We need its *return type* to resolve the subsequent `.mul`.
980            match resolve_simple_call_v2(&name, caller_contract_name_opt, graph, ctx) {
981                Ok(ResolvedTarget::Function {
982                    contract_name,
983                    function_name,
984                    ..
985                }) => {
986                    trace!("[Resolve Type V2]   Identifier '{}' resolved to function '{:?}.{}'. Getting return type.", name, contract_name, function_name);
987                    let func_key = (contract_name.clone(), function_name.clone());
988                    if let Some(node_id) = graph.node_lookup.get(&func_key) {
989                        let def_node_info_opt = ctx
990                            .definition_nodes_info
991                            .iter()
992                            .find(|(id, _, _)| *id == *node_id)
993                            .map(|(_, info, _)| info.clone());
994                        if let Some(def_node_info) = def_node_info_opt {
995                            // Use the function's return type as the expression's type
996                            let return_type = get_function_return_type_v2(&def_node_info, input)?;
997                            trace!(
998                                "[Resolve Type V2]     Function return type: {:?}",
999                                return_type
1000                            );
1001                            return Ok(return_type);
1002                        } else {
1003                            trace!("[Resolve Type V2]     Could not find definition NodeInfo for function ID {}", node_id);
1004                        }
1005                    } else {
1006                        trace!("[Resolve Type V2]     Could not find node ID in graph lookup for function key {:?}", func_key);
1007                    }
1008                }
1009                Ok(_) | Err(_) => {
1010                    // Didn't resolve to a function, or an error occurred during resolution.
1011                    // Continue with the original "not resolved" path.
1012                    trace!(
1013                        "[Resolve Type V2]   Identifier '{}' did not resolve to a function in scope.",
1014                        name
1015                    );
1016                }
1017            }
1018
1019            trace!(
1020                "[Resolve Type V2]   Identifier '{}' type not resolved (state/type/local/function?).",
1021                name
1022            );
1023            Ok(None) // Identifier not resolved by any means
1024        }
1025        "primitive_type" => Ok(Some(expr_text)),
1026        "type_cast_expression" => {
1027            // Type is the type being cast to
1028            let type_node = expr_node.child(0).ok_or_else(|| {
1029                TypeError::MissingChild("type_cast_expression missing type node".to_string())
1030            })?;
1031            let type_name = get_node_text(&type_node, source).to_string();
1032            trace!(
1033                "[Resolve Type V2]   Type cast expression resolves to type '{}'",
1034                type_name
1035            );
1036            Ok(Some(type_name))
1037        }
1038        "string_literal" => Ok(Some("string".to_string())),
1039        "number_literal" => Ok(Some("uint256".to_string())),
1040        "boolean_literal" => Ok(Some("bool".to_string())),
1041        "hex_literal" => Ok(Some("bytes".to_string())),
1042        "address_literal" => Ok(Some("address".to_string())),
1043
1044        // --- Recursive Cases ---
1045        "member_expression" => resolve_property_type_from_node(
1046            expr_node,
1047            caller_node_id,
1048            caller_contract_name_opt,
1049            ctx,
1050            graph,
1051            source,
1052            solidity_lang,
1053            input,
1054        ),
1055        "call_expression" => {
1056            // The type of a call expression is the return type of the function being called.
1057            // We need to analyze the call to find the target and then its return type.
1058            trace!(
1059                "[Resolve Type V2]   Call expression: Analyzing call to determine return type."
1060            );
1061            // Use analyze_chained_call on the call_expression itself.
1062            // This seems recursive, be careful. Maybe analyze_chained_call should return the final type?
1063            // Let's try calling analyze_chained_call and taking the result_type of the last step.
1064            let steps = analyze_chained_call(
1065                expr_node, // Analyze the call expression itself
1066                caller_node_id,
1067                caller_contract_name_opt,
1068                ctx,
1069                graph,
1070                source,
1071                solidity_lang,
1072                input,
1073                None,                   // original_start_node_for_err_reporting
1074                expr_node.start_byte(), // Pass the start byte of this call expression as the originating span start
1075            )?;
1076
1077            if let Some(last_step) = steps.last() {
1078                trace!(
1079                    "[Resolve Type V2]   Call expression resolved. Last step result type: {:?}",
1080                    last_step.result_type
1081                );
1082                Ok(last_step.result_type.clone())
1083            } else {
1084                trace!("[Resolve Type V2]   Call expression analysis yielded no steps. Cannot determine type.");
1085                if let Some(func_node) = expr_node.child_by_field_name("function") {
1086                    if func_node.kind() == "expression"
1087                        && func_node
1088                            .child(0)
1089                            .map_or(false, |n| n.kind() == "identifier")
1090                    {
1091                        let id_node = func_node.child(0).unwrap();
1092                        let name = get_node_text(&id_node, source).to_string();
1093                        if ctx.all_interfaces.contains_key(&name) {
1094                            trace!("[Resolve Type V2]   Re-identified as interface type cast to '{}' (no steps generated)", name);
1095                            return Ok(Some(name)); // Return the interface type
1096                        }
1097                    }
1098                }
1099                Ok(None) // Truly unresolved
1100            }
1101        }
1102        "new_expression" => {
1103            // Type is the contract being constructed
1104            let type_name_node = expr_node.named_child(0).ok_or_else(|| {
1105                TypeError::MissingChild(format!(
1106                    "new_expression missing type_name (named_child(0) failed for expr_node: {})",
1107                    get_node_text(&expr_node, source)
1108                ))
1109            })?;
1110            let contract_name = get_node_text(&type_name_node, source).to_string(); // Simplified
1111            trace!(
1112                "[Resolve Type V2]   New expression resolves to type '{}'",
1113                contract_name
1114            );
1115            Ok(Some(contract_name))
1116        }
1117        "expression" => {
1118            // Delegate type resolution to the first child of the expression node
1119            if let Some(child_node) = expr_node.child(0) {
1120                trace!(
1121                    "[Resolve Type V2]   Delegating 'expression' type resolution to child kind: '{}'",
1122                    child_node.kind()
1123                );
1124                // Recursively call resolve_expression_type_v2 on the child
1125                resolve_expression_type_v2(
1126                    child_node,
1127                    caller_node_id,
1128                    caller_contract_name_opt,
1129                    ctx,
1130                    graph,
1131                    source,
1132                    solidity_lang,
1133                    input,
1134                )
1135            } else {
1136                trace!("[Resolve Type V2]   'expression' node has no children.");
1137                Ok(None) // Expression node with no children has no type
1138            }
1139        }
1140        // TODO: Handle tuple_expression, etc.
1141        "array_access" => {
1142            let base_node = expr_node.child_by_field_name("base").ok_or_else(|| {
1143                TypeError::MissingChild(format!(
1144                    "array_access missing base for node: {}",
1145                    get_node_text(&expr_node, source)
1146                ))
1147            })?;
1148            trace!(
1149                "[Resolve Type V2]   Array access: base kind '{}', text '{}'",
1150                base_node.kind(),
1151                get_node_text(&base_node, source)
1152            );
1153
1154            let base_type_opt = resolve_expression_type_v2(
1155                base_node,
1156                caller_node_id,
1157                caller_contract_name_opt,
1158                ctx,
1159                graph,
1160                source,
1161                solidity_lang,
1162                input,
1163            )?;
1164
1165            if let Some(ref base_type_of_base_expr_str) = base_type_opt {
1166                // Use `ref` to borrow
1167                trace!(
1168                    "[Resolve Type V2]     Base type of array_access resolved to: '{}'",
1169                    base_type_of_base_expr_str
1170                );
1171
1172                // Priority 1: If base is an identifier, check ctx.contract_mappings for precise value_type
1173                if base_node.kind() == "identifier" {
1174                    if let Some(contract_name) = caller_contract_name_opt {
1175                        let mapping_var_name = get_node_text(&base_node, source);
1176                        if let Some(mapping_info) = ctx
1177                            .contract_mappings
1178                            .get(&(contract_name.clone(), mapping_var_name.to_string()))
1179                        {
1180                            trace!(
1181                                "[Resolve Type V2]       Array access on known mapping '{}.{}'. Resolved value type from MappingInfo: '{}'",
1182                                contract_name, mapping_var_name, mapping_info.value_type
1183                            );
1184                            return Ok(Some(mapping_info.value_type.clone()));
1185                        }
1186                    }
1187                }
1188
1189                // Priority 2: Fallback to parsing the resolved base_type_of_base_expr_str
1190                if base_type_of_base_expr_str.starts_with("mapping(") {
1191                    match parse_mapping_final_value_type(base_type_of_base_expr_str) {
1192                        Ok(final_value_type) => {
1193                            trace!(
1194                                "[Resolve Type V2]       Parsed mapping type string '{}'. Final value type: '{}'",
1195                                base_type_of_base_expr_str, final_value_type
1196                            );
1197                            return Ok(Some(final_value_type));
1198                        }
1199                        Err(e) => {
1200                            trace!(
1201                                "[Resolve Type V2]       Error parsing mapping type string '{}': {}. Falling back.",
1202                                base_type_of_base_expr_str, e
1203                            );
1204                            // Fall through to other checks if parsing fails, or return error
1205                            // For now, let's try to be robust and let other checks proceed.
1206                        }
1207                    }
1208                }
1209
1210                // Existing logic for arrays and bytes
1211                if base_type_of_base_expr_str.ends_with("[]") {
1212                    let element_type =
1213                        base_type_of_base_expr_str[0..base_type_of_base_expr_str.len() - 2].trim();
1214                    if !element_type.is_empty() {
1215                        trace!(
1216                            "[Resolve Type V2]       Array element type: '{}'",
1217                            element_type
1218                        );
1219                        return Ok(Some(element_type.to_string()));
1220                    }
1221                    trace!(
1222                        "[Resolve Type V2]       Could not parse array type: {}",
1223                        base_type_of_base_expr_str // Fix: Use new variable name
1224                    );
1225                    Ok(None)
1226                } else if base_type_of_base_expr_str == "bytes" {
1227                    // Fix: Use new variable name
1228                    // Indexing bytes returns bytes1
1229                    trace!("[Resolve Type V2]       Bytes indexed, returning 'bytes1'");
1230                    return Ok(Some("bytes1".to_string()));
1231                }
1232                // Add other special cases if needed (e.g., string)
1233                else {
1234                    trace!(
1235                            "[Resolve Type V2]     Base type '{}' is not a mapping, array, or bytes. Cannot determine indexed type.",
1236                            base_type_of_base_expr_str // Fix: Use new variable name
1237                        );
1238                    Ok(None)
1239                }
1240            } else {
1241                trace!("[Resolve Type V2]     Could not resolve base type of array_access.");
1242                Ok(None)
1243            }
1244        }
1245        "binary_expression" => {
1246            // --- Determine result type based on operator ---
1247            // We don't need to analyze operands recursively here, just determine the result type.
1248            let operator_node = expr_node.child_by_field_name("operator").ok_or_else(|| {
1249                TypeError::MissingChild("binary_expression missing operator".to_string())
1250            })?;
1251            let operator_text = get_node_text(&operator_node, source);
1252
1253            let result_type = match &*operator_text {
1254                // Use &* to dereference String to &str
1255                ">" | "<" | ">=" | "<=" | "==" | "!=" => Some("bool".to_string()),
1256                "&&" | "||" => Some("bool".to_string()),
1257                "+" | "-" | "*" | "/" | "%" | "**" | "&" | "|" | "^" | "<<" | ">>" => {
1258                    // Assume uint256 for arithmetic/bitwise/shift for now
1259                    Some("uint256".to_string())
1260                }
1261                _ => {
1262                    trace!(
1263                        "[Resolve Type V2]   Unhandled binary operator: '{}'",
1264                        operator_text
1265                    );
1266                    None // Unknown operator type
1267                }
1268            };
1269            return Ok(result_type); // Return the determined type
1270        }
1271        "unary_expression" => {
1272            // Basic heuristic: Assume numeric ops return uint256, boolean ops return bool
1273            let operator = expr_node
1274                .child_by_field_name("operator")
1275                .map(|n| get_node_text(&n, source));
1276            let operand_node = expr_node.child_by_field_name("argument").ok_or_else(|| {
1277                TypeError::MissingChild("unary_expression missing argument".to_string())
1278            })?;
1279            match operator.as_deref() {
1280                Some("!") => Ok(Some("bool".to_string())),
1281                Some("-") | Some("+") | Some("++") | Some("--") => Ok(Some("uint256".to_string())), // Assume numeric
1282                _ => {
1283                    trace!(
1284                        "[Resolve Type V2]   Unhandled unary operator: {:?}",
1285                        operator
1286                    );
1287                    // Fallback to operand type?
1288                    resolve_expression_type_v2(
1289                        operand_node,
1290                        caller_node_id,
1291                        caller_contract_name_opt,
1292                        ctx,
1293                        graph,
1294                        source,
1295                        solidity_lang,
1296                        input,
1297                    )
1298                }
1299            }
1300        }
1301        _ => {
1302            trace!(
1303                "[Resolve Type V2]   Unhandled expression kind: {}",
1304                expr_node.kind()
1305            );
1306            Ok(None)
1307        }
1308    }
1309}
1310
1311/// Resolves the type of a property accessed via member expression (`object.property`).
1312fn resolve_property_type<'a>(
1313    object_type_name: &str,
1314    property_name: &str,
1315    caller_contract_name_opt: &'a Option<String>, // Prefixed, seems unused
1316    graph: &'a CallGraph,
1317    ctx: &CallGraphGeneratorContext,
1318    _source: &'a str,                          // Prefixed, seems unused
1319    _solidity_lang: &'a tree_sitter::Language, // Prefixed, seems unused
1320    input: &'a CallGraphGeneratorInput,        // Pass input for potential use in future
1321) -> std::result::Result<Option<String>, TypeError> {
1322    // Use full path to Result
1323    trace!(
1324        "[Resolve Property Type] Resolving type for property '{}' on object type '{}'",
1325        property_name, object_type_name
1326    );
1327
1328    // 1. Check if the object type is a contract/library/interface and the property is a state variable
1329    // Note: Interfaces don't have state variables.
1330    if !ctx.all_interfaces.contains_key(object_type_name) {
1331        if let Some(type_name) = ctx
1332            .state_var_types
1333            .get(&(object_type_name.to_string(), property_name.to_string()))
1334        {
1335            trace!(
1336                "[Resolve Property Type]   Property resolved to state var type '{}'",
1337                type_name
1338            );
1339            return Ok(Some(type_name.clone()));
1340        }
1341    }
1342
1343    // 2. Check if the object type is a contract/library/interface and the property is a function/method
1344    // We need the *return type* of that function.
1345    let function_key = (
1346        Some(object_type_name.to_string()),
1347        property_name.to_string(),
1348    );
1349    if let Some(target_node_id) = graph.node_lookup.get(&function_key) {
1350        trace!("[Resolve Property Type]   Property resolved to function member. Node ID: {}. Getting return type.", target_node_id);
1351        // Use the existing helper from cg.rs (or move/adapt it later)
1352        // Need to get the definition TsNode for the target function
1353        let target_def_node_opt = ctx
1354            .definition_nodes_info
1355            .iter()
1356            .find(|(id, _, _)| *id == *target_node_id)
1357            .map(|(_, n, _)| n.clone());
1358        if let Some(target_def_node) = target_def_node_opt {
1359            let return_type = get_function_return_type_v2(&target_def_node, input)?; // Use V2 helper
1360            trace!(
1361                "[Resolve Property Type]     Return type from get_function_return_type_v2: {:?}",
1362                return_type
1363            );
1364            return Ok(return_type);
1365        } else {
1366            trace!("[Resolve Property Type]     Could not find definition node for target function ID {}", target_node_id);
1367            return Ok(None); // Cannot determine return type without definition node
1368        }
1369    }
1370
1371    // 3. Check 'using for' libraries (if the property is a function in an attached library)
1372    // This requires finding the library function and getting its return type.
1373    if let Some(library_target) = find_using_for_target(
1374        object_type_name,
1375        property_name,
1376        caller_contract_name_opt,
1377        graph,
1378        ctx,
1379    )? {
1380        if let ResolvedTarget::Function {
1381            contract_name: Some(lib_name),
1382            function_name,
1383            ..
1384        } = library_target
1385        {
1386            let lib_func_key = (Some(lib_name.clone()), function_name.clone());
1387            if let Some(target_node_id) = graph.node_lookup.get(&lib_func_key) {
1388                trace!("[Resolve Property Type]   Property resolved to 'using for' library function '{}.{}'. Node ID: {}. Getting return type.", lib_name, function_name, target_node_id);
1389                let target_def_node_opt = ctx
1390                    .definition_nodes_info
1391                    .iter()
1392                    .find(|(id, _, _)| *id == *target_node_id)
1393                    .map(|(_, n, _)| n.clone());
1394                if let Some(target_def_node) = target_def_node_opt {
1395                    let return_type = get_function_return_type_v2(&target_def_node, input)?;
1396                    trace!("[Resolve Property Type]     Return type from get_function_return_type_v2: {:?}", return_type);
1397                    return Ok(return_type);
1398                } else {
1399                    trace!("[Resolve Property Type]     Could not find definition node for target library function ID {}", target_node_id);
1400                    return Ok(None);
1401                }
1402            }
1403        }
1404    }
1405
1406    // 4. Check built-in properties (e.g., array.length -> uint256, address.balance -> uint256)
1407    // TODO: Implement built-in property type resolution
1408    if property_name == "length"
1409        && (object_type_name.ends_with("[]")
1410            || object_type_name == "bytes"
1411            || object_type_name == "string")
1412    {
1413        return Ok(Some("uint256".to_string()));
1414    }
1415    if property_name == "balance" && object_type_name == "address" {
1416        return Ok(Some("uint256".to_string()));
1417    }
1418
1419    trace!(
1420        "[Resolve Property Type]   Property '{}' type not resolved within type '{}'.",
1421        property_name, object_type_name
1422    );
1423    Ok(None)
1424}
1425
1426/// Helper to resolve property type directly from a member_expression node.
1427fn resolve_property_type_from_node<'a>(
1428    member_expr_node: TsNode<'a>,
1429    caller_node_id: usize,
1430    caller_contract_name_opt: &'a Option<String>,
1431    ctx: &CallGraphGeneratorContext,
1432    graph: &'a CallGraph,
1433    source: &'a str,
1434    solidity_lang: &'a tree_sitter::Language,
1435    input: &'a CallGraphGeneratorInput, // Pass input for potential use in future
1436) -> std::result::Result<Option<String>, TypeError> {
1437    // Use full path to Result
1438    let object_node = member_expr_node
1439        .child_by_field_name("object")
1440        .ok_or_else(|| TypeError::MissingChild("member_expression missing object".to_string()))?;
1441    let property_node = member_expr_node
1442        .child_by_field_name("property")
1443        .ok_or_else(|| TypeError::MissingChild("member_expression missing property".to_string()))?;
1444
1445    if property_node.kind() != "identifier" {
1446        trace!("[Resolve Type V2]   Member expr: Property is not an identifier.");
1447        return Ok(None);
1448    }
1449    let property_name = get_node_text(&property_node, source).to_string();
1450
1451    // Resolve the type of the object first
1452    let object_type_name_opt = resolve_expression_type_v2(
1453        object_node,
1454        caller_node_id,
1455        caller_contract_name_opt,
1456        ctx,
1457        graph,
1458        source,
1459        solidity_lang,
1460        input,
1461    )?;
1462
1463    if let Some(object_type_name) = object_type_name_opt {
1464        // Now resolve the property type based on the object type
1465        resolve_property_type(
1466            &object_type_name,
1467            &property_name,
1468            caller_contract_name_opt,
1469            graph,
1470            ctx,
1471            source,
1472            solidity_lang,
1473            input,
1474        )
1475    } else {
1476        trace!("[Resolve Type V2]   Member expr: Could not resolve object type.");
1477        Ok(None) // Object type couldn't be resolved
1478    }
1479}
1480
1481/// Resolves the target of a member access or library call (V2).
1482/// Returns a `ResolvedTarget` enum.
1483fn resolve_member_or_library_call_v2<'a>(
1484    object_type_name: &str,
1485    property_name: &str,
1486    caller_contract_name_opt: &'a Option<String>,
1487    graph: &'a CallGraph,
1488    ctx: &CallGraphGeneratorContext,
1489    source: &'a str, // Needed for interface resolution heuristic potentially
1490    call_span_bytes: (usize, usize), // For error reporting
1491) -> std::result::Result<ResolvedTarget, TypeError> {
1492    // Use full path to Result
1493    let call_span_text = &source[call_span_bytes.0..call_span_bytes.1];
1494    trace!(
1495        "[Resolve Member/Lib V2] Resolving: Type='{}', Property='{}', CallerScope='{:?}', Span='{}'",
1496        object_type_name, property_name, caller_contract_name_opt, call_span_text
1497    );
1498
1499    // --- Priority 1: Interface Implementation Resolution (if object type is an interface) ---
1500    if ctx.all_interfaces.contains_key(object_type_name) {
1501        trace!(
1502            "[Resolve Member/Lib V2]   Object type '{}' is an interface. Looking for implementations.",
1503            object_type_name
1504        );
1505        let interface_name = object_type_name; // Alias for clarity
1506        let method_name = property_name; // Alias for clarity
1507
1508        // Find contracts directly implementing this interface
1509        let mut implementing_contracts = Vec::new();
1510        for (contract, implemented_interfaces) in &ctx.contract_implements {
1511            if implemented_interfaces.contains(&interface_name.to_string()) {
1512                implementing_contracts.push(contract.clone());
1513            }
1514        }
1515        trace!(
1516            "[Resolve Member/Lib V2]     Directly implementing contracts found: {:?}",
1517            implementing_contracts
1518        );
1519
1520        let mut potential_direct_targets = Vec::new();
1521        for contract_name_impl in &implementing_contracts {
1522            let target_key = (Some(contract_name_impl.clone()), method_name.to_string());
1523            if let Some(node_id) = graph.node_lookup.get(&target_key).copied() {
1524                if let Some(node) = graph.nodes.get(node_id) {
1525                    potential_direct_targets.push(ResolvedTarget::Function {
1526                        contract_name: Some(contract_name_impl.clone()),
1527                        function_name: method_name.to_string(),
1528                        node_type: node.node_type.clone(),
1529                    });
1530                }
1531            }
1532        }
1533
1534        // Attempt Natspec-driven binding resolution
1535        let mut natspec_binding_provided_concrete_impl: Option<ResolvedTarget> = None;
1536        let mut natspec_key_found_but_binding_non_concrete = false;
1537
1538        if let (Some(manifest_ref), Some(registry_ref)) =
1539            (ctx.manifest.as_ref(), ctx.binding_registry.as_ref())
1540        {
1541            let resolver =
1542                crate::interface_resolver::InterfaceResolver::new(manifest_ref, registry_ref);
1543            let interface_file_path_hint: Option<std::path::PathBuf> = manifest_ref
1544                .entries
1545                .iter()
1546                .find(|entry| {
1547                    entry.item_kind == crate::natspec::extract::SourceItemKind::Interface
1548                        && entry.item_name.as_deref() == Some(interface_name)
1549                })
1550                .map(|entry| entry.file_path.clone());
1551
1552            trace!(
1553                "[Resolve Member/Lib V2]       Interface file hint for binding lookup: {:?}",
1554                interface_file_path_hint
1555            );
1556
1557            match resolver.resolve_for_item_name_kind(
1558                interface_name,
1559                crate::natspec::extract::SourceItemKind::Interface,
1560                interface_file_path_hint.as_deref(),
1561            ) {
1562                Ok(Some(binding_config)) => {
1563                    trace!("[Resolve Member/Lib V2]       Natspec binding key '{}' found for interface '{}'.", binding_config.key, interface_name);
1564                    if let Some(concrete_contract_name) = &binding_config.contract_name {
1565                        trace!("[Resolve Member/Lib V2]         Binding config specifies concrete contract: '{}'", concrete_contract_name);
1566                        let bound_target_key = (
1567                            Some(concrete_contract_name.clone()),
1568                            method_name.to_string(),
1569                        );
1570                        if let Some(node_id) = graph.node_lookup.get(&bound_target_key) {
1571                            if let Some(node_info) = graph.nodes.get(*node_id) {
1572                                match node_info.node_type {
1573                                    NodeType::Function | NodeType::Modifier | NodeType::Constructor => {
1574                                        let bound_implementation = ResolvedTarget::Function {
1575                                            contract_name: Some(concrete_contract_name.clone()),
1576                                            function_name: method_name.to_string(),
1577                                            node_type: node_info.node_type.clone(),
1578                                        };
1579                                        trace!("[Resolve Member/Lib V2]           Successfully resolved to bound callable implementation: {:?}", bound_implementation);
1580                                        natspec_binding_provided_concrete_impl = Some(ResolvedTarget::InterfaceMethod {
1581                                            interface_name: interface_name.to_string(),
1582                                            method_name: method_name.to_string(),
1583                                            implementation: Some(Box::new(bound_implementation)),
1584                                        });
1585                                    }
1586                                    NodeType::StorageVariable => {
1587                                        if node_info.visibility == crate::cg::Visibility::Public {
1588                                            let getter_implementation = ResolvedTarget::Function {
1589                                                contract_name: Some(concrete_contract_name.clone()),
1590                                                function_name: method_name.to_string(), // Getter name is var name
1591                                                node_type: NodeType::Function, // The getter is conceptually a function
1592                                            };
1593                                            trace!("[Resolve Member/Lib V2]           Successfully resolved to getter for public storage variable: {:?}", getter_implementation);
1594                                            natspec_binding_provided_concrete_impl = Some(ResolvedTarget::InterfaceMethod {
1595                                                interface_name: interface_name.to_string(),
1596                                                method_name: method_name.to_string(),
1597                                                implementation: Some(Box::new(getter_implementation)),
1598                                            });
1599                                        } else {
1600                                            trace!("[Resolve Member/Lib V2]           Bound contract '{}' member '{}' is a non-public StorageVariable. Binding not used.", concrete_contract_name, method_name);
1601                                        }
1602                                    }
1603                                    _ => trace!("[Resolve Member/Lib V2]           Bound contract '{}' member '{}' is not callable or a public storage variable (Type: {:?}). Binding not used.", concrete_contract_name, method_name, node_info.node_type),
1604                                }
1605                            } else {
1606                                trace!("[Resolve Member/Lib V2]           Bound contract '{}' method '{}' (Node ID {}) not found in graph.nodes. Binding not used.", concrete_contract_name, method_name, node_id);
1607                            }
1608                        } else { // node_id not found in graph.node_lookup
1609                            trace!("[Resolve Member/Lib V2]           Method or Variable '{}' not found in bound contract '{}' via graph.node_lookup. Binding not used.", method_name, concrete_contract_name);
1610                        }
1611                    } else {
1612                        trace!("[Resolve Member/Lib V2]         Binding for interface '{}' (key '{}') has no concrete_contract_name.", interface_name, binding_config.key);
1613                        natspec_key_found_but_binding_non_concrete = true;
1614                    }
1615                }
1616                Ok(None) => trace!("[Resolve Member/Lib V2]       No Natspec binding key found for interface type '{}', or key not in registry.", interface_name),
1617                Err(e) => trace!("Warning: Error resolving Natspec binding for interface type '{}': {}. Proceeding as if no binding found.", interface_name, e),
1618            }
1619        } else {
1620            trace!("[Resolve Member/Lib V2]     Manifest or BindingRegistry not available for Natspec-driven interface binding resolution.");
1621        }
1622
1623        // Decision logic based on binding and direct implementations
1624        if let Some(concrete_impl) = natspec_binding_provided_concrete_impl {
1625            return Ok(concrete_impl);
1626        }
1627
1628        // Natspec binding did not provide a concrete implementation. Consider direct ones.
1629        if potential_direct_targets.len() == 1 {
1630            let direct_implementation = potential_direct_targets.remove(0);
1631            trace!("[Resolve Member/Lib V2]     Resolved to single direct implementation (Natspec binding was not concrete or not found): {:?}", direct_implementation);
1632            return Ok(ResolvedTarget::InterfaceMethod {
1633                interface_name: interface_name.to_string(),
1634                method_name: method_name.to_string(),
1635                implementation: Some(Box::new(direct_implementation)),
1636            });
1637        }
1638
1639        if potential_direct_targets.len() > 1 {
1640            if natspec_key_found_but_binding_non_concrete {
1641                trace!("[Resolve Member/Lib V2]     Ambiguous direct implementations for '{}.{}', but Natspec binding key was found (though non-concrete). Falling back to abstract.", interface_name, method_name);
1642                return Ok(ResolvedTarget::InterfaceMethod {
1643                    interface_name: interface_name.to_string(),
1644                    method_name: method_name.to_string(),
1645                    implementation: None,
1646                });
1647            } else {
1648                // Multiple direct implementers AND (no Natspec binding key found OR Natspec key lookup failed entirely).
1649                trace!("[Resolve Member/Lib V2]     Ambiguous implementation for '{}.{}' ({} direct targets) and no overriding/clarifying Natspec binding. Implementations: {:?}", interface_name, method_name, potential_direct_targets.len(), implementing_contracts);
1650                return Err(TypeError::AmbiguousInterfaceImplementation {
1651                    interface_name: interface_name.to_string(),
1652                    method_name: method_name.to_string(),
1653                    implementations: implementing_contracts,
1654                });
1655            }
1656        }
1657
1658        // potential_direct_targets.len() == 0 and Natspec binding did not provide a concrete implementation.
1659        trace!("[Resolve Member/Lib V2]     No specific implementation found for '{}.{}'. Assuming external or abstract.", interface_name, method_name);
1660        return Ok(ResolvedTarget::InterfaceMethod {
1661            interface_name: interface_name.to_string(),
1662            method_name: method_name.to_string(),
1663            implementation: None,
1664        });
1665    }
1666
1667    // --- Priority 2: Direct Member Lookup (Contract/Library) ---
1668    let direct_lookup_key = (
1669        Some(object_type_name.to_string()),
1670        property_name.to_string(),
1671    );
1672    let lookup_result = graph.node_lookup.get(&direct_lookup_key);
1673    trace!(
1674        "[Resolve Member/Lib V2]   Attempting direct lookup with key: {:?}. Found: {}",
1675        direct_lookup_key,
1676        lookup_result.is_some()
1677    );
1678    if let Some(node_id) = lookup_result {
1679        // Use the captured result
1680        if let Some(node) = graph.nodes.get(*node_id) {
1681            // Ensure it's a callable type (Function, Modifier, Constructor)
1682            match node.node_type {
1683                NodeType::Function | NodeType::Modifier | NodeType::Constructor => {
1684                    trace!(
1685                        "[Resolve Member/Lib V2]   Found direct member: Node ID {}, Type: {:?}",
1686                        node_id, node.node_type
1687                    );
1688                    return Ok(ResolvedTarget::Function {
1689                        contract_name: node.contract_name.clone(),
1690                        function_name: node.name.clone(),
1691                        node_type: node.node_type.clone(),
1692                    });
1693                }
1694                _ => {
1695                    trace!("[Resolve Member/Lib V2]   Found direct member but it's not callable (Type: {:?}). Node ID {}", node.node_type, node_id);
1696                    // It's a state variable or other non-callable member.
1697                    return Ok(ResolvedTarget::NotCallable {
1698                        reason: format!(
1699                            "Member '{}' is a {:?}, not a function.",
1700                            property_name, node.node_type
1701                        ),
1702                    });
1703                }
1704            }
1705        }
1706    } else {
1707        trace!(
1708            "[Resolve Member/Lib V2]   Direct lookup failed for key: {:?}",
1709            direct_lookup_key
1710        );
1711    }
1712
1713    // --- Priority 3: 'using for' Directives ---
1714    if let Some(library_target) = find_using_for_target(
1715        object_type_name,
1716        property_name,
1717        caller_contract_name_opt,
1718        graph,
1719        ctx,
1720    )? {
1721        trace!(
1722            "[Resolve Member/Lib V2]   Found via 'using for': {:?}",
1723            library_target
1724        );
1725        // library_target is already the ResolvedTarget due to `if let Some()`
1726        return Ok(library_target);
1727    }
1728
1729    // --- Priority 4: Built-in Members ---
1730    if let Some(builtin) = builtin::get_builtin(property_name, object_type_name) {
1731        trace!(
1732            "[Resolve Member/Lib V2]   Resolved to built-in member '{}' for type '{}'",
1733            builtin.name, builtin.object_type
1734        );
1735        return Ok(ResolvedTarget::BuiltIn {
1736            object_type: object_type_name.to_string(), // Keep the specific type (e.g., uint[])
1737            name: builtin.name.to_string(),
1738        });
1739    }
1740
1741    // --- Not Found ---
1742    trace!(
1743        "[Resolve Member/Lib V2]   Target '{}.{}' could not be resolved.",
1744        object_type_name, property_name
1745    );
1746    Ok(ResolvedTarget::NotCallable {
1747        reason: format!(
1748            "Member or method '{}.{}' not found.",
1749            object_type_name, property_name
1750        ),
1751    })
1752}
1753
1754/// Helper to specifically find a target using 'using for' directives.
1755fn find_using_for_target<'a>(
1756    object_type_name: &str,
1757    property_name: &str,
1758    caller_contract_name_opt: &'a Option<String>,
1759    graph: &'a CallGraph,
1760    ctx: &CallGraphGeneratorContext,
1761) -> std::result::Result<Option<ResolvedTarget>, TypeError> {
1762    // Use full path to Result
1763    let mut potential_libraries = Vec::new();
1764    let mut types_to_check = vec![object_type_name.to_string()];
1765
1766    if object_type_name == "uint" {
1767        types_to_check.push("uint256".to_string());
1768    } else if object_type_name == "uint256" {
1769        types_to_check.push("uint".to_string());
1770    }
1771
1772    // Check contract-specific directives first
1773    if let Some(caller_contract_name) = caller_contract_name_opt {
1774        for type_name in &types_to_check {
1775            // Iterate through original type and alias
1776            // Check for specific type: (Some(Contract), Type)
1777            let specific_type_key = (
1778                Some(caller_contract_name.clone()),
1779                type_name.clone(), // Use type_name from loop
1780            );
1781            trace!(
1782                "[Find UsingFor] Checking specific key: {:?}",
1783                specific_type_key
1784            ); // DEBUG
1785            if let Some(libs) = ctx.using_for_directives.get(&specific_type_key) {
1786                trace!(
1787                    "[Find UsingFor]   Found specific directive for contract '{}', type '{}': {:?}",
1788                    caller_contract_name,
1789                    type_name,
1790                    libs // Use type_name
1791                );
1792                potential_libraries.extend(libs.iter().cloned());
1793            }
1794        }
1795        // Check for wildcard type: (Some(Contract), "*") - Only check once
1796        let wildcard_key = (Some(caller_contract_name.clone()), "*".to_string());
1797        trace!("[Find UsingFor] Checking wildcard key: {:?}", wildcard_key); // DEBUG
1798        if let Some(libs) = ctx.using_for_directives.get(&wildcard_key) {
1799            trace!(
1800                "[Find UsingFor]   Found wildcard directive for contract '{}': {:?}",
1801                caller_contract_name, libs
1802            );
1803            potential_libraries.extend(libs.iter().cloned());
1804        }
1805    } else {
1806        trace!(
1807            "[Find UsingFor]   No caller contract name provided. Skipping contract-specific directives."
1808        );
1809    }
1810    // TODO: Check global 'using for' directives (key: (None, Type) and (None, "*"))
1811
1812    trace!(
1813        "[Find UsingFor]   All potential libraries before dedup: {:?}",
1814        potential_libraries
1815    );
1816
1817    // Remove duplicates and check libraries
1818    potential_libraries.sort_unstable();
1819    potential_libraries.dedup();
1820
1821    trace!(
1822        "[Find UsingFor]   Potential libraries after dedup: {:?}", // DEBUG
1823        potential_libraries
1824    );
1825
1826    if !potential_libraries.is_empty() {
1827        trace!(
1828            "[Find UsingFor] Checking libraries for '{}.{}': {:?}",
1829            object_type_name,
1830            property_name,
1831            potential_libraries // Log original type name here
1832        );
1833        for library_name in potential_libraries {
1834            let library_method_key = (Some(library_name.clone()), property_name.to_string());
1835            trace!(
1836                "[Find UsingFor]   Checking library key: {:?}",
1837                library_method_key
1838            ); // DEBUG
1839            if let Some(id) = graph.node_lookup.get(&library_method_key) {
1840                if let Some(node) = graph.nodes.get(*id) {
1841                    // Found a match in a library
1842                    trace!(
1843                        "[Find UsingFor]   Found match in library '{}': Node ID {}",
1844                        library_name, id
1845                    );
1846                    // Solidity ambiguity rules usually prevent multiple libs defining the same function for the same type.
1847                    // Return the first one found.
1848                    return Ok(Some(ResolvedTarget::Function {
1849                        contract_name: Some(library_name.clone()), // Library name acts as scope
1850                        function_name: property_name.to_string(),
1851                        node_type: node.node_type.clone(), // Should be Function
1852                    }));
1853                }
1854            } else {
1855                trace!(
1856                    "[Find UsingFor]     Lookup failed for key: {:?}",
1857                    library_method_key
1858                ); // DEBUG
1859            }
1860        }
1861    }
1862    trace!("[Find UsingFor]   No match found via 'using for'."); // DEBUG
1863    Ok(None) // Not found via using for
1864}
1865
1866/// Parses a mapping type string to extract its final (potentially nested) value type.
1867/// e.g., "mapping(address => mapping(uint => IMyInterface))" -> "IMyInterface"
1868/// e.g., "mapping(address => uint)" -> "uint"
1869/// e.g., "uint" -> "uint"
1870fn parse_mapping_final_value_type(type_str: &str) -> Result<String, String> {
1871    let trimmed_type_str = type_str.trim();
1872    if trimmed_type_str.starts_with("mapping(") && trimmed_type_str.ends_with(')') {
1873        // Find the main "=>" separating key type from value type.
1874        // We need to balance parentheses to correctly find the top-level arrow.
1875        let mut balance = 0;
1876        let mut arrow_index_opt: Option<usize> = None;
1877
1878        // Iterate from after "mapping(" up to before the final ")"
1879        let content_start = "mapping(".len();
1880        let content_end = trimmed_type_str.len() - ")".len();
1881
1882        if content_start >= content_end {
1883            return Err(format!("Invalid mapping string (too short): {}", type_str));
1884        }
1885
1886        for (i, char_code) in trimmed_type_str
1887            .char_indices()
1888            .skip(content_start)
1889            .take(content_end - content_start)
1890        {
1891            if char_code == '(' {
1892                balance += 1;
1893            } else if char_code == ')' {
1894                balance -= 1;
1895                if balance < 0 {
1896                    // Unbalanced parentheses before finding arrow
1897                    return Err(format!(
1898                        "Malformed mapping string (unbalanced parentheses): {}",
1899                        type_str
1900                    ));
1901                }
1902            } else if char_code == '=' && balance == 0 {
1903                // Check if next char is '>'
1904                if trimmed_type_str.chars().nth(i + 1) == Some('>') {
1905                    arrow_index_opt = Some(i);
1906                    break;
1907                }
1908            }
1909        }
1910
1911        if let Some(arrow_idx) = arrow_index_opt {
1912            // Ensure "=>" is followed by something.
1913            if arrow_idx + 2 >= content_end {
1914                return Err(format!(
1915                    "Malformed mapping string (no value type after '=>'): {}",
1916                    type_str
1917                ));
1918            }
1919            let value_type_part = trimmed_type_str[arrow_idx + 2..content_end].trim();
1920            // Recursively parse the value part, as it could be another mapping
1921            parse_mapping_final_value_type(value_type_part)
1922        } else {
1923            Err(format!(
1924                "Malformed mapping string (could not find top-level '=>'): {}",
1925                type_str
1926            ))
1927        }
1928    } else {
1929        // Base case: not a mapping, so this is the final value type
1930        Ok(trimmed_type_str.to_string())
1931    }
1932}
1933
1934/// Resolves the target of a simple call (identifier only).
1935fn resolve_simple_call_v2<'a>(
1936    name: &str,
1937    caller_contract_name_opt: &'a Option<String>,
1938    graph: &'a CallGraph,
1939    ctx: &CallGraphGeneratorContext,
1940) -> std::result::Result<ResolvedTarget, TypeError> {
1941    // Use full path to Result
1942    trace!(
1943        "[Resolve Simple V2] Resolving simple call '{}', CallerScope='{:?}'",
1944        name, caller_contract_name_opt
1945    );
1946
1947    // 1. Look within the current contract scope (if any)
1948    if let Some(contract_name) = caller_contract_name_opt {
1949        let key = (Some(contract_name.clone()), name.to_string());
1950        if let Some(id) = graph.node_lookup.get(&key) {
1951            if let Some(node) = graph.nodes.get(*id) {
1952                trace!(
1953                    "[Resolve Simple V2]   Found in contract scope: Node ID {}, Type: {:?}",
1954                    id, node.node_type
1955                );
1956                return Ok(ResolvedTarget::Function {
1957                    contract_name: node.contract_name.clone(),
1958                    function_name: node.name.clone(),
1959                    node_type: node.node_type.clone(),
1960                });
1961            }
1962        }
1963        // --- Start: Check inherited contracts ---
1964        if let Some(inherited_names) = ctx.contract_inherits.get(contract_name) {
1965            trace!(
1966                "[Resolve Simple V2]   Checking inheritance for '{}': {:?}",
1967                contract_name, inherited_names
1968            );
1969            for base_name in inherited_names {
1970                let base_key = (Some(base_name.clone()), name.to_string());
1971                if let Some(id) = graph.node_lookup.get(&base_key) {
1972                    if let Some(node) = graph.nodes.get(*id) {
1973                        trace!("[Resolve Simple V2]     Found in base contract '{}': Node ID {}, Type: {:?}", base_name, id, node.node_type);
1974                        // TODO: Handle potential ambiguity if found in multiple bases? For now, return first found.
1975                        // TODO: Handle visibility checks for inherited functions.
1976                        return Ok(ResolvedTarget::Function {
1977                            contract_name: node.contract_name.clone(), // Use the base contract's name
1978                            function_name: node.name.clone(),
1979                            node_type: node.node_type.clone(),
1980                        });
1981                    }
1982                }
1983            }
1984        }
1985    }
1986
1987    // 2. Look for a free function (no contract scope)
1988    let free_function_key = (None, name.to_string());
1989    if let Some(id) = graph.node_lookup.get(&free_function_key) {
1990        if let Some(node) = graph.nodes.get(*id) {
1991            trace!(
1992                "[Resolve Simple V2]   Found free function: Node ID {}, Type: {:?}",
1993                id, node.node_type
1994            );
1995            return Ok(ResolvedTarget::Function {
1996                contract_name: None,
1997                function_name: node.name.clone(),
1998                node_type: node.node_type.clone(),
1999            });
2000        }
2001    }
2002
2003    // 3. Check if it's a constructor call (matches a contract name)
2004    if ctx.all_contracts.contains_key(name) {
2005        let constructor_key = (Some(name.to_string()), name.to_string());
2006        if graph.node_lookup.contains_key(&constructor_key) {
2007            trace!(
2008                "[Resolve Simple V2]   Found constructor for contract '{}'",
2009                name
2010            );
2011            return Ok(ResolvedTarget::Function {
2012                contract_name: Some(name.to_string()),
2013                function_name: name.to_string(),
2014                node_type: NodeType::Constructor,
2015            });
2016        }
2017    }
2018
2019    // Not found
2020    trace!(
2021        "[Resolve Simple V2]   Simple call target '{}' not found.",
2022        name
2023    );
2024    Err(TypeError::TargetResolutionFailed {
2025        name: name.to_string(),
2026        reason: "Function or constructor not found in current scope or globally.".to_string(),
2027    })
2028}
2029
2030/// Extracts argument text from a call expression or emit statement node.
2031fn extract_arguments_v2<'a>(node: TsNode<'a>, source: &'a str) -> Vec<String> {
2032    let mut argument_texts: Vec<String> = Vec::new();
2033    let mut cursor = node.walk();
2034    for child_node in node.children(&mut cursor) {
2035        // Arguments are typically within a node like 'arguments' or directly as 'call_argument'
2036        let mut search_queue = VecDeque::new();
2037        search_queue.push_back(child_node);
2038
2039        while let Some(current) = search_queue.pop_front() {
2040            if current.kind() == "call_argument" {
2041                if let Some(expression_node) = current.child(0) {
2042                    argument_texts.push(get_node_text(&expression_node, source).to_string());
2043                }
2044            } else {
2045                // Recursively check children if not a direct argument node
2046                let mut inner_cursor = current.walk();
2047                for inner_child in current.children(&mut inner_cursor) {
2048                    // Avoid infinite loops, maybe limit depth or check node kind
2049                    if inner_child.kind() != node.kind() {
2050                        // Simple cycle check
2051                        search_queue.push_back(inner_child);
2052                    }
2053                }
2054            }
2055        }
2056    }
2057    argument_texts
2058}
2059
2060/// Attempts to parse the return type string from a function definition node (V2).
2061/// Handles single types, basic tuples, and named return parameters.
2062fn get_function_return_type_v2(
2063    definition_node_info: &NodeInfo,
2064    input: &CallGraphGeneratorInput, // Add input
2065) -> std::result::Result<Option<String>, TypeError> {
2066    // Use full path to Result
2067    // Query to find the actual type name node within the standard return structure.
2068    // Handles simple cases like `returns (uint)`. Does not handle named returns yet.
2069    // Added pattern for interface function definitions.
2070    let query_str = r#"
2071        (function_definition
2072          return_type: (return_type_definition
2073            (parameter type: (type_name) @return_type_name_node)
2074          )
2075        )
2076
2077        ; Interface function definitions might have a slightly different structure
2078        ; but the return type part should be similar.
2079        (interface_declaration (contract_body (function_definition
2080          return_type: (return_type_definition
2081            (parameter type: (type_name) @return_type_name_node))
2082        )))
2083    "#;
2084    // Use lang and source from input
2085    let query = Query::new(&input.solidity_lang, query_str)?;
2086    let mut cursor = QueryCursor::new();
2087    let source_bytes = input.source.as_bytes();
2088
2089    // Retrieve the TsNode using the span from NodeInfo and the tree from input
2090    let definition_ts_node = input
2091        .tree
2092        .root_node()
2093        .descendant_for_byte_range(definition_node_info.span.0, definition_node_info.span.1)
2094        .ok_or_else(|| {
2095            // Use TypeError::Internal or a more specific error
2096            TypeError::Internal(format!(
2097                "Failed to find definition TsNode for span {:?}",
2098                definition_node_info.span
2099            ))
2100        })?;
2101
2102    let mut matches = cursor.matches(
2103        &query,
2104        definition_ts_node, // Query only within this node
2105        |node: TsNode| std::iter::once(&source_bytes[node.byte_range()]),
2106    );
2107    // Use while let Some() for streaming iterator
2108    while let Some(match_) = matches.next() {
2109        // Find the @return_type_name_node capture
2110        for capture in match_.captures {
2111            // Use capture_names() slice to get the name by index
2112            if query.capture_names()[capture.index as usize] == "return_type_name_node" {
2113                let type_name_node = capture.node;
2114                let type_name_text = get_node_text(&type_name_node, &input.source).to_string();
2115
2116                if !type_name_text.is_empty() {
2117                    trace!(
2118                        "[Get Return Type V2] Found single return type name: '{}'",
2119                        type_name_text
2120                    );
2121                    // TODO: Handle multiple return types if the query is extended later
2122                    return Ok(Some(type_name_text)); // Return the captured type name text
2123                } else {
2124                    trace!("[Get Return Type V2] Found empty return type name node.");
2125                    // Fall through to return None if text is empty
2126                }
2127            }
2128        }
2129    }
2130
2131    trace!("[Get Return Type V2] No return type name node captured or parsed.");
2132    Ok(None) // No return type found or parsed
2133}
2134
2135/// Resolves the return type of a call based on its ResolvedTarget.
2136fn resolve_call_return_type<'a>(
2137    target: &ResolvedTarget,
2138    ctx: &CallGraphGeneratorContext,
2139    graph: &'a CallGraph,
2140    source: &'a str,
2141    solidity_lang: &'a tree_sitter::Language,
2142    input: &'a CallGraphGeneratorInput, // Pass input for potential use in future
2143) -> std::result::Result<Option<String>, TypeError> {
2144    // Use full path to Result
2145    match target {
2146        ResolvedTarget::Function {
2147            contract_name,
2148            function_name,
2149            node_type: _,
2150        } => {
2151            // Use _ for node_type as we check graph
2152            let lookup_key = (contract_name.clone(), function_name.clone());
2153            if let Some(node_id) = graph.node_lookup.get(&lookup_key) {
2154                let graph_node = &graph.nodes[*node_id]; // This is the node found by (Scope, Name)
2155
2156                match graph_node.node_type {
2157                    NodeType::Function | NodeType::Modifier | NodeType::Constructor => {
2158                        // Prioritize graph_node.declared_return_type if available
2159                        if let Some(declared_type) = &graph_node.declared_return_type {
2160                            trace!("[Resolve Call Return Type] Using declared_return_type '{}' for Node ID {}", declared_type, *node_id);
2161                            return Ok(Some(declared_type.clone()));
2162                        }
2163                        // Fallback to parsing TsNode via definition_nodes_info
2164                        let def_node_info_opt = ctx
2165                            .definition_nodes_info
2166                            .iter()
2167                            .find(|(id, _, _)| *id == *node_id)
2168                            .map(|(_, info, _)| info.clone());
2169                        if let Some(def_node_info) = def_node_info_opt {
2170                            trace!("[Resolve Call Return Type] Falling back to get_function_return_type_v2 for Node ID {}", *node_id);
2171                            return get_function_return_type_v2(&def_node_info, input);
2172                        }
2173                        trace!("[Resolve Call Return Type] No declared_return_type and no NodeInfo for Node ID {}", *node_id);
2174                        return Ok(None);
2175                    }
2176                    NodeType::StorageVariable => {
2177                        if graph_node.visibility == crate::cg::Visibility::Public {
2178                            let actual_contract_name =
2179                                graph_node.contract_name.as_ref().ok_or_else(|| {
2180                                    TypeError::Internal(
2181                                        "StorageVariable node missing contract name".to_string(),
2182                                    )
2183                                })?;
2184                            let var_name = &graph_node.name;
2185                            trace!("[Resolve Call Return Type] ResolvedTarget::Function points to public StorageVariable '{}.{}'. Determining getter return type.", actual_contract_name, var_name);
2186
2187                            // For mappings, the getter returns the final value type.
2188                            if let Some(mapping_info) = ctx
2189                                .contract_mappings
2190                                .get(&(actual_contract_name.clone(), var_name.clone()))
2191                            {
2192                                trace!("[Resolve Call Return Type]   Found mapping info. Value type: '{}'", mapping_info.value_type);
2193                                return Ok(Some(mapping_info.value_type.clone()));
2194                            }
2195                            // For other public state variables, the getter returns the variable's type.
2196                            if let Some(type_str) = ctx
2197                                .state_var_types
2198                                .get(&(actual_contract_name.clone(), var_name.clone()))
2199                            {
2200                                trace!("[Resolve Call Return Type]   Found state_var_types info. Type: '{}'", type_str);
2201                                return Ok(Some(type_str.clone()));
2202                            }
2203                            trace!("[Resolve Call Return Type]   Public StorageVariable {}.{} type info not found in contract_mappings or state_var_types.", actual_contract_name, var_name);
2204                            return Err(TypeError::Internal(format!(
2205                                "Public StorageVariable {}.{} type info not found.",
2206                                actual_contract_name, var_name
2207                            )));
2208                        } else {
2209                            trace!("[Resolve Call Return Type] ResolvedTarget::Function points to non-public StorageVariable '{}.{}'. No getter.", graph_node.contract_name.as_deref().unwrap_or("?"), graph_node.name);
2210                            return Ok(None);
2211                        }
2212                    }
2213                    _ => {
2214                        trace!("[Resolve Call Return Type] Warning: Node for '{:?}' (ID {}) is of unexpected type {:?}.", lookup_key, *node_id, graph_node.node_type);
2215                        return Ok(None);
2216                    }
2217                }
2218            } else {
2219                trace!("[Resolve Call Return Type] Warning: Node for '{:?}' not found in graph.node_lookup.", lookup_key);
2220                return Ok(None);
2221            }
2222        }
2223        ResolvedTarget::InterfaceMethod {
2224            interface_name,
2225            method_name,
2226            implementation,
2227        } => {
2228            // If a concrete implementation was found, use its return type.
2229            if let Some(impl_target) = implementation {
2230                resolve_call_return_type(impl_target, ctx, graph, source, solidity_lang, input)
2231            } else {
2232                // Find the interface method definition node and parse its return type
2233                let _iface_key = (Some(interface_name.clone()), interface_name.clone()); // Key for interface node itself (Mark as unused for now)
2234                let method_key = (Some(interface_name.clone()), method_name.clone()); // Key for method node
2235                if let Some(method_node_id) = graph.node_lookup.get(&method_key) {
2236                    let def_node_opt = ctx
2237                        .definition_nodes_info
2238                        .iter()
2239                        .find(|(id, _, _)| *id == *method_node_id)
2240                        .map(|(_, n, _)| n.clone());
2241                    if let Some(def_node) = def_node_opt {
2242                        get_function_return_type_v2(&def_node, input)
2243                    } else {
2244                        Ok(None) // Cannot find definition node for interface method
2245                    }
2246                } else {
2247                    Ok(None) // Interface method not found in graph
2248                }
2249            }
2250        }
2251        ResolvedTarget::BuiltIn { object_type, name } => {
2252            // Use the builtin module to get the return type string
2253            if let Some(return_type_str) = builtin::get_builtin_return_type(name, object_type) {
2254                if return_type_str == "void" {
2255                    Ok(None) // Map "void" to None
2256                } else {
2257                    Ok(Some(return_type_str.to_string()))
2258                }
2259            } else {
2260                trace!(
2261                    "[Resolve Return Type] Warning: Could not find return type for known built-in {}.{}",
2262                    object_type, name
2263                );
2264                Ok(None) // Fallback if lookup fails for some reason
2265            }
2266        }
2267        ResolvedTarget::NotCallable { .. } => Ok(None), // Not callable, no return type
2268        ResolvedTarget::External { .. } => Ok(None), // Cannot determine return type of external call
2269        ResolvedTarget::TypeCast { type_name } => {
2270            // The result type of a type cast is the type name itself.
2271            Ok(Some(type_name.clone()))
2272        }
2273    }
2274}
2275
2276#[cfg(test)]
2277mod tests {
2278    use super::*;
2279    use crate::cg::{
2280        CallGraph, CallGraphGeneratorContext, CallGraphGeneratorInput, CallGraphGeneratorPipeline,
2281    }; // Import necessary items
2282    use crate::steps::CallsHandling;
2283    use crate::steps::ContractHandling;
2284    use anyhow::{Context, Result};
2285    use crate::parser::get_solidity_language;
2286    use std::collections::HashMap;
2287    // use std::sync::Arc; // Removed unused import
2288    use tree_sitter::{Parser, Tree};
2289
2290    // Test-specific imports for manifest and binding
2291    use crate::interface_resolver::{BindingConfig, BindingRegistry}; // Removed BindingFile
2292    use crate::manifest::{Manifest, ManifestEntry};
2293    use crate::natspec::extract::SourceItemKind as NatspecSourceItemKind;
2294    use crate::natspec::{TextIndex as NatspecTextIndex, TextRange as NatspecTextRange};
2295
2296    // Helper to create a basic context and graph for testing
2297    fn setup_test_environment(
2298        source: &str,
2299    ) -> Result<(
2300        CallGraphGeneratorContext,
2301        CallGraph,
2302        Tree,
2303        tree_sitter::Language,
2304        CallGraphGeneratorInput,
2305    )> {
2306        let solidity_lang = get_solidity_language();
2307        let mut parser = Parser::new();
2308        parser
2309            .set_language(&solidity_lang)
2310            .context("Failed to set language")?;
2311        let tree = parser
2312            .parse(source, None)
2313            .context("Failed to parse source code")?;
2314
2315        let mut ctx = CallGraphGeneratorContext::default();
2316        let mut graph = CallGraph::new();
2317
2318        // --- Populate context and graph using the pipeline ---
2319
2320        let input = CallGraphGeneratorInput {
2321            source: source.to_string(),
2322            tree: tree.clone(),
2323            solidity_lang: solidity_lang.clone(),
2324        };
2325        let input_clone = input.clone();
2326        let mut pipeline = CallGraphGeneratorPipeline::new();
2327        pipeline.add_step(Box::new(ContractHandling::default()));
2328        pipeline.add_step(Box::new(CallsHandling::default()));
2329        let config: HashMap<String, String> = HashMap::new(); // Empty config for tests
2330        pipeline
2331            .run(input, &mut ctx, &mut graph, &config)
2332            .context("Pipeline execution failed")?;
2333        // --- End population ---
2334
2335        Ok((
2336            ctx.clone(),
2337            graph.clone(),
2338            tree.clone(),
2339            solidity_lang.clone(),
2340            input_clone, // Return the input for use in tests
2341        ))
2342    }
2343
2344    // More controlled setup for specific manifest/binding tests
2345    fn setup_test_environment_customized(
2346        source: &str,
2347        custom_manifest: Option<Manifest>,
2348        custom_binding_registry: Option<BindingRegistry>,
2349        // We still run parts of the pipeline to populate basic graph structure
2350        // like nodes for contracts and functions.
2351    ) -> Result<(
2352        CallGraphGeneratorContext,
2353        CallGraph,
2354        Tree,
2355        tree_sitter::Language,
2356        CallGraphGeneratorInput,
2357    )> {
2358        let solidity_lang = get_solidity_language();
2359        let mut parser = Parser::new();
2360        parser
2361            .set_language(&solidity_lang)
2362            .context("Failed to set language")?;
2363        let tree = parser
2364            .parse(source, None)
2365            .context("Failed to parse source code")?;
2366
2367        let mut ctx = CallGraphGeneratorContext::default();
2368        let mut graph = CallGraph::new();
2369
2370        // Populate from custom arguments
2371        if let Some(manifest) = custom_manifest {
2372            ctx.manifest = Some(manifest);
2373        }
2374        if let Some(registry) = custom_binding_registry {
2375            ctx.binding_registry = Some(registry);
2376        }
2377
2378        // Run a minimal pipeline to get basic graph structure (contracts, functions)
2379        // This will also populate ctx.all_contracts, ctx.all_interfaces, etc.
2380        // and graph.node_lookup, graph.nodes.
2381        let input = CallGraphGeneratorInput {
2382            source: source.to_string(),
2383            tree: tree.clone(),
2384            solidity_lang: solidity_lang.clone(),
2385        };
2386        let input_clone = input.clone();
2387        let mut pipeline = CallGraphGeneratorPipeline::new();
2388        pipeline.add_step(Box::new(ContractHandling::default())); // Gets contract/interface/function nodes
2389                                                                  // We might not need CallsHandling if we are testing resolution logic directly
2390                                                                  // and not relying on its graph modifications.
2391                                                                  // pipeline.add_step(Box::new(CallsHandling::default()));
2392        let config: HashMap<String, String> = HashMap::new();
2393        pipeline
2394            .run(input, &mut ctx, &mut graph, &config)
2395            .context("Minimal pipeline execution failed for custom setup")?;
2396
2397        // Crucially, after the pipeline runs and populates ctx.all_contracts etc.,
2398        // we might need to re-apply our custom manifest and registry if the pipeline
2399        // overwrote or modified them in an undesired way for the test's specific scenario.
2400        // For this test, ContractHandling should populate all_contracts/interfaces correctly.
2401        // The binding registry and manifest are primarily *inputs* to the chains.rs logic.
2402
2403        Ok((
2404            ctx.clone(),
2405            graph.clone(),
2406            tree.clone(),
2407            solidity_lang.clone(),
2408            input_clone,
2409        ))
2410    }
2411    // Helper to find a specific node within the source code for testing
2412    fn find_node_by_kind_and_text<'a>(
2413        tree: &'a Tree,
2414        source: &'a str,
2415        kind: &str,
2416        text: &str,
2417    ) -> Option<TsNode<'a>> {
2418        let mut cursor = tree.walk();
2419        loop {
2420            let node = cursor.node();
2421            if node.kind() == kind && get_node_text(&node, source) == text {
2422                return Some(node);
2423            }
2424            if !cursor.goto_first_child() {
2425                while !cursor.goto_next_sibling() {
2426                    if !cursor.goto_parent() {
2427                        return None; // Reached root without finding
2428                    }
2429                }
2430            }
2431        }
2432    }
2433
2434    // Helper to find a function definition node by its name identifier using a query
2435    fn find_function_definition_node_by_name<'a>(
2436        tree: &'a Tree,
2437        source: &'a str,
2438        lang: &'a tree_sitter::Language,
2439        function_name: &str,
2440    ) -> Result<TsNode<'a>> {
2441        // Use anyhow::Result
2442        let query_str = r#"
2443            (function_definition
2444              name: (identifier) @function_name
2445            )
2446        "#;
2447        let query = Query::new(lang, query_str).context("Failed to create function name query")?;
2448        let mut cursor = QueryCursor::new();
2449        let source_bytes = source.as_bytes();
2450
2451        let mut matches = cursor.matches(&query, tree.root_node(), |node: TsNode| {
2452            std::iter::once(&source_bytes[node.byte_range()])
2453        });
2454
2455        // Use while let Some() for streaming iterator
2456        while let Some(match_) = matches.next() {
2457            for capture in match_.captures {
2458                // Use capture_names() slice to get the name by index
2459                if query.capture_names()[capture.index as usize] == "function_name" {
2460                    let name_node = capture.node;
2461                    if get_node_text(&name_node, source) == function_name {
2462                        // Found the name node, now get its parent (the function_definition)
2463                        return name_node.parent().ok_or_else(|| {
2464                            anyhow::anyhow!(
2465                                // Use anyhow::anyhow!
2466                                "Failed to get parent of function name identifier '{}'",
2467                                function_name
2468                            )
2469                        });
2470                    }
2471                }
2472            }
2473        }
2474
2475        Err(anyhow::anyhow!(
2476            // Use anyhow::anyhow!
2477            "Function definition node for '{}' not found",
2478            function_name
2479        ))
2480    }
2481
2482    // Helper to find the Nth node of a specific kind
2483    fn find_nth_node_of_kind<'a>(tree: &'a Tree, kind: &str, n: usize) -> Option<TsNode<'a>> {
2484        let mut cursor = tree.walk();
2485        let mut count = 0;
2486        loop {
2487            let node = cursor.node();
2488            if node.kind() == kind {
2489                if count == n {
2490                    return Some(node);
2491                }
2492                count += 1;
2493            }
2494            if !cursor.goto_first_child() {
2495                while !cursor.goto_next_sibling() {
2496                    if !cursor.goto_parent() {
2497                        return None; // Reached root without finding
2498                    }
2499                }
2500            }
2501        }
2502    }
2503
2504    // Helper to find the Nth descendant node of a specific kind using a query (Revised with captures)
2505    fn find_nth_descendant_node_of_kind<'a>(
2506        start_node: &TsNode<'a>, // Start search from this node
2507        source: &'a str,
2508        lang: &'a tree_sitter::Language,
2509        kind: &str,
2510        n: usize,
2511    ) -> Result<Option<TsNode<'a>>> {
2512        // Return Result<Option<...>>
2513        let query_str = format!("({}) @target_kind", kind);
2514        let query = Query::new(lang, &query_str)
2515            .with_context(|| format!("Failed to create query for kind '{}'", kind))?;
2516        let mut cursor = QueryCursor::new();
2517        let source_bytes = source.as_bytes();
2518
2519        // Use captures which iterates through all captures for all matches within the start_node
2520        let mut captures = cursor.captures(
2521            &query,
2522            start_node.clone(), // Query within the start_node
2523            |node: TsNode| std::iter::once(&source_bytes[node.byte_range()]),
2524        );
2525
2526        let mut count = 0;
2527        // Use while let Some to iterate over QueryCaptures
2528        while let Some((match_, capture_index)) = captures.next() {
2529            // We only have one capture name "@target_kind", index 0
2530            // Dereference capture_index for comparison
2531            if *capture_index == 0 {
2532                // Dereference capture_index for indexing
2533                let node = match_.captures[*capture_index].node; // Get the node from the capture
2534                trace!(
2535                    // DEBUG
2536                    "[find_nth_descendant] Found node: Kind='{}', Span=({},{}), Count={}",
2537                    node.kind(),
2538                    node.start_byte(),
2539                    node.end_byte(),
2540                    count
2541                );
2542                if count == n {
2543                    trace!("[find_nth_descendant] Returning node at index {}", n); // DEBUG
2544                    return Ok(Some(node)); // Found the nth descendant
2545                }
2546                count += 1;
2547            }
2548        }
2549        trace!(
2550            "[find_nth_descendant] Node of kind '{}' at index {} not found.",
2551            kind, n
2552        ); // DEBUG
2553        Ok(None) // Nth descendant not found
2554    }
2555
2556    #[test]
2557    fn test_resolve_simple_identifier_type() -> Result<()> {
2558        // Use our own Result type
2559        let source = r#"
2560            contract Test {
2561                uint256 stateVar;
2562                bool flag;
2563                function testFunc(uint256 local) public {
2564                    stateVar = 1; // Access state var
2565                    flag = true;  // Access state var
2566                    uint256 x = local; // Access local var
2567                }
2568            }
2569        "#;
2570        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
2571        // Find the first function definition node (testFunc)
2572        let _func_def_node = find_nth_node_of_kind(&tree, "function_definition", 0)
2573            .expect("missing function definition");
2574        let caller_node_id = graph
2575            .node_lookup
2576            .get(&(Some("Test".to_string()), "testFunc".to_string()))
2577            .copied()
2578            .unwrap();
2579
2580        // Find the identifier nodes within the function body
2581        let state_var_node =
2582            find_node_by_kind_and_text(&tree, source, "identifier", "stateVar").unwrap();
2583        let flag_node = find_node_by_kind_and_text(&tree, source, "identifier", "flag").unwrap();
2584        // let local_node = find_node_by_kind_and_text(&tree, source, "identifier", "local").unwrap(); // Need 2nd instance
2585
2586        // Resolve state variables
2587        let type1 = resolve_expression_type_v2(
2588            state_var_node,
2589            caller_node_id,
2590            &Some("Test".to_string()),
2591            &ctx,
2592            &graph,
2593            source,
2594            &lang,
2595            &input,
2596        )?;
2597        assert_eq!(type1, Some("uint256".to_string()));
2598
2599        let type2 = resolve_expression_type_v2(
2600            flag_node,
2601            caller_node_id,
2602            &Some("Test".to_string()),
2603            &ctx,
2604            &graph,
2605            source,
2606            &lang,
2607            &input,
2608        )?;
2609        assert_eq!(type2, Some("bool".to_string()));
2610
2611        // TODO: Test local variable resolution once implemented
2612        // let type3 = resolve_expression_type_v2(local_node, caller_node_id, &Some("Test".to_string()), &ctx, &graph, source, &lang)?;
2613        // assert_eq!(type3, Some("uint256".to_string()));
2614
2615        Ok(())
2616    }
2617
2618    #[test]
2619    fn test_resolve_member_access_type() -> Result<()> {
2620        // Use our own Result type
2621        let source = r#"
2622            contract C { uint public x; }
2623            contract Test {
2624                C c_instance;
2625                function getX() public view returns (uint) {
2626                    return c_instance.x; // Member access
2627                }
2628            }
2629        "#;
2630        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
2631        let _func_node = find_nth_node_of_kind(&tree, "function_definition", 0)
2632            .expect("missing function definition");
2633        // Find the member_expression node for c_instance.x
2634        let _member_expr_node = find_nth_node_of_kind(&tree, "member_expression", 0)
2635            .expect("missing member expression"); // Add expect
2636        let caller_node_id = graph
2637            .node_lookup
2638            .get(&(Some("Test".to_string()), "getX".to_string()))
2639            .copied()
2640            .unwrap();
2641
2642        // Find the member_expression node for c_instance.x
2643        let member_expr_node = find_nth_node_of_kind(&tree, "member_expression", 0)
2644            .expect("missing member expression"); // Add expect
2645
2646        let resolved_type = resolve_expression_type_v2(
2647            member_expr_node,
2648            caller_node_id,
2649            &Some("Test".to_string()),
2650            &ctx,
2651            &graph,
2652            source,
2653            &lang,
2654            &input,
2655        )?;
2656        assert_eq!(resolved_type, Some("uint".to_string()));
2657
2658        Ok(())
2659    }
2660
2661    #[test]
2662    fn test_analyze_simple_call() -> Result<()> {
2663        // Use our own Result type
2664        let source = r#"
2665            contract Test {
2666                function target() internal pure returns (uint) { return 1; }
2667                function caller() public pure {
2668                    target(); // Simple call
2669                }
2670            }
2671        "#;
2672        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
2673        // Use the new helper function to find the function definition node
2674        let _caller_def_node =
2675            find_function_definition_node_by_name(&tree, source, &lang, "caller")
2676                .expect("missing function definition");
2677        let caller_node_id = graph
2678            .node_lookup
2679            .get(&(Some("Test".to_string()), "caller".to_string()))
2680            .copied()
2681            .unwrap();
2682
2683        // Find the call_expression node for target()
2684        let call_expr_node = find_nth_node_of_kind(&tree, "call_expression", 0)
2685            .expect("missing call expression"); // Add expect
2686
2687        let steps = analyze_chained_call(
2688            call_expr_node,
2689            caller_node_id,
2690            &Some("Test".to_string()),
2691            &ctx,
2692            &graph,
2693            source,
2694            &lang,
2695            &input,
2696            None,                        // This is the top-level call
2697            call_expr_node.start_byte(), // Add originating_span_start
2698        )?;
2699
2700        assert_eq!(steps.len(), 1);
2701        let step = &steps[0];
2702        assert_eq!(step.object_type, None);
2703        assert_eq!(step.result_type, Some("uint".to_string())); // target returns uint
2704        assert!(step.arguments.is_empty());
2705        match &step.target {
2706            ResolvedTarget::Function {
2707                contract_name,
2708                function_name,
2709                node_type,
2710            } => {
2711                assert_eq!(contract_name.as_deref(), Some("Test"));
2712                assert_eq!(function_name, "target");
2713                assert_eq!(*node_type, NodeType::Function);
2714            }
2715            _ => panic!("Expected ResolvedTarget::Function, got {:?}", step.target),
2716        }
2717
2718        Ok(())
2719    }
2720
2721    #[test]
2722    fn test_analyze_member_call() -> Result<()> {
2723        // Use our own Result type
2724        let source = r#"
2725             contract C {
2726                 function method() public pure returns (bool) { return true; }
2727             }
2728             contract Test {
2729                 C c_instance;
2730                 function caller() public {
2731                     c_instance.method(); // Member call
2732                 }
2733             }
2734         "#;
2735        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
2736        // Use the correct helper to find the function definition node by its name
2737        let caller_def_node = find_function_definition_node_by_name(&tree, source, &lang, "caller")
2738            .expect("missing function definition");
2739        let caller_node_id = graph
2740            .node_lookup
2741            .get(&(Some("Test".to_string()), "caller".to_string()))
2742            .copied()
2743            .unwrap();
2744
2745        // Find the call_expression node for c_instance.method() within the caller function
2746        // Use the new helper function starting from the caller's definition node
2747        let call_expr_node_opt = find_nth_descendant_node_of_kind(
2748            // Changed to Option
2749            &caller_def_node, // Start search within the caller function node
2750            source,
2751            &lang,
2752            "call_expression", // Kind to find
2753            0,                 // Find the first one (index 0)
2754        )?; // Propagate Result error
2755
2756        let call_expr_node = call_expr_node_opt.ok_or_else(|| { // Convert Option to Result
2757                TypeError::Internal(format!(
2758                    "Could not find the call_expression node for c_instance.method() within caller function node: {:?}",
2759                    caller_def_node.byte_range()
2760                ))
2761            })?; // Propagate Option error
2762
2763        let steps = analyze_chained_call(
2764            call_expr_node,
2765            caller_node_id,
2766            &Some("Test".to_string()),
2767            &ctx,
2768            &graph,
2769            source,
2770            &lang,
2771            &input,
2772            None,
2773            call_expr_node.start_byte(), // Add originating_span_start
2774        )?;
2775
2776        assert_eq!(steps.len(), 1);
2777        let step = &steps[0];
2778        assert_eq!(step.object_type, Some("C".to_string())); // Type of c_instance is C
2779        assert_eq!(step.result_type, Some("bool".to_string())); // method returns bool
2780        assert!(step.arguments.is_empty());
2781        match &step.target {
2782            ResolvedTarget::Function {
2783                contract_name,
2784                function_name,
2785                node_type,
2786            } => {
2787                assert_eq!(contract_name.as_deref(), Some("C"));
2788                assert_eq!(function_name, "method");
2789                assert_eq!(*node_type, NodeType::Function);
2790            }
2791            _ => panic!("Expected ResolvedTarget::Function, got {:?}", step.target),
2792        }
2793
2794        Ok(())
2795    }
2796
2797    #[test]
2798    fn test_analyze_using_for_call() -> Result<()> {
2799        // Use our own Result type
2800        let source = r#"
2801            library SafeMath {
2802                function add(uint a, uint b) internal pure returns (uint) { return a+b; }
2803            }
2804            contract Test {
2805                using SafeMath for uint;
2806                uint value;
2807                function caller(uint y) public {
2808                    value = value.add(y); // Using for call
2809                }
2810             }
2811         "#;
2812        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
2813        let _caller_def_node =
2814            find_function_definition_node_by_name(&tree, source, &lang, "caller")
2815                .expect("missing function definition");
2816        let caller_node_id = graph
2817            .node_lookup
2818            .get(&(Some("Test".to_string()), "caller".to_string()))
2819            .copied()
2820            .unwrap();
2821
2822        // Find the call_expression node for value.add(y)
2823        let call_expr_node = find_nth_node_of_kind(&tree, "call_expression", 0).unwrap();
2824
2825        let steps = analyze_chained_call(
2826            call_expr_node,
2827            caller_node_id,
2828            &Some("Test".to_string()),
2829            &ctx,
2830            &graph,
2831            source,
2832            &lang,
2833            &input,
2834            None,
2835            call_expr_node.start_byte(), // Add originating_span_start
2836        )?;
2837
2838        assert_eq!(steps.len(), 1);
2839        let step = &steps[0];
2840        // The state var 'value' is declared as 'uint', so the resolved object type is "uint"
2841        assert_eq!(step.object_type, Some("uint".to_string()));
2842        assert_eq!(step.result_type, Some("uint".to_string())); // add returns uint
2843        assert_eq!(step.arguments, vec!["y".to_string()]);
2844        match &step.target {
2845            ResolvedTarget::Function {
2846                contract_name,
2847                function_name,
2848                node_type,
2849            } => {
2850                assert_eq!(contract_name.as_deref(), Some("SafeMath")); // Resolved to library
2851                assert_eq!(function_name, "add");
2852                assert_eq!(*node_type, NodeType::Function);
2853            }
2854            _ => panic!(
2855                "Expected ResolvedTarget::Function (Library), got {:?}",
2856                step.target
2857            ),
2858        }
2859
2860        Ok(())
2861    }
2862
2863    #[test]
2864    fn test_analyze_interface_call_single_impl() -> Result<()> {
2865        // Use our own Result type
2866        let source = r#"
2867            interface ICounter { function increment() external; }
2868            contract Counter is ICounter { function increment() external override {} }
2869            contract Test {
2870                ICounter c;
2871                function caller() public {
2872                    c.increment(); // Interface call
2873                }
2874            }
2875        "#;
2876        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
2877        let _caller_def_node = find_function_definition_node_by_name(&tree, source, &lang, "caller")
2878            .expect("missing function definition");
2879        let caller_node_id = graph
2880            .node_lookup
2881            .get(&(Some("Test".to_string()), "caller".to_string()))
2882            .copied()
2883            .unwrap();
2884
2885        // Find the call_expression node for c.increment()
2886        let call_expr_node = find_nth_node_of_kind(&tree, "call_expression", 0).unwrap();
2887
2888        let steps = analyze_chained_call(
2889            call_expr_node,
2890            caller_node_id,
2891            &Some("Test".to_string()),
2892            &ctx,
2893            &graph,
2894            source,
2895            &lang,
2896            &input,
2897            None,
2898            call_expr_node.start_byte(), // Add originating_span_start
2899        )?;
2900
2901        assert_eq!(steps.len(), 1);
2902        let step = &steps[0];
2903        assert_eq!(step.object_type, Some("ICounter".to_string())); // Type of c is ICounter
2904        assert_eq!(step.result_type, None); // increment returns nothing
2905        assert!(step.arguments.is_empty());
2906        match &step.target {
2907            ResolvedTarget::InterfaceMethod {
2908                interface_name,
2909                method_name,
2910                implementation,
2911            } => {
2912                assert_eq!(interface_name, "ICounter");
2913                assert_eq!(method_name, "increment");
2914                assert!(implementation.is_some());
2915                // Check the implementation details
2916                match implementation.as_deref() {
2917                    Some(ResolvedTarget::Function {
2918                        contract_name,
2919                        function_name,
2920                        node_type,
2921                    }) => {
2922                        assert_eq!(contract_name.as_deref(), Some("Counter")); // Resolved to implementation
2923                        assert_eq!(function_name, "increment");
2924                        assert_eq!(*node_type, NodeType::Function);
2925                    }
2926                    _ => panic!(
2927                        "Expected implementation to be ResolvedTarget::Function, got {:?}",
2928                        implementation
2929                    ),
2930                }
2931            }
2932            _ => panic!(
2933                "Expected ResolvedTarget::InterfaceMethod, got {:?}",
2934                step.target
2935            ),
2936        }
2937
2938        Ok(())
2939    }
2940
2941    #[test]
2942    fn test_analyze_chained_call_using_for() -> Result<()> {
2943        // Use our own Result type
2944        let source = r#"
2945            library SafeMath {
2946                function add(uint a, uint b) internal pure returns (uint) { return a+b; }
2947                function sub(uint a, uint b) internal pure returns (uint) { return a-b; }
2948            }
2949            contract Test {
2950                using SafeMath for uint;
2951                uint value;
2952                function caller(uint x, uint y) public {
2953                     value = value.add(x).sub(y); // Chained using for call
2954                 }
2955            }
2956        "#;
2957        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
2958
2959        let _caller_def_node =
2960            find_function_definition_node_by_name(&tree, source, &lang, "caller")
2961                .expect("missing function definition");
2962        let caller_node_id = graph
2963            .node_lookup
2964            .get(&(Some("Test".to_string()), "caller".to_string()))
2965            .copied()
2966            .unwrap();
2967
2968        // Find the outer call_expression node for value.add(x).sub(y) by navigating from the assignment
2969        // Re-fetch caller_def_node as it was prefixed with _
2970        let caller_def_node_for_search =
2971            find_function_definition_node_by_name(&tree, source, &lang, "caller")
2972                .expect("missing function definition");
2973        let assignment_node = find_nth_descendant_node_of_kind(
2974            &caller_def_node_for_search, // Search within the caller function
2975            source,
2976            &lang,
2977            "assignment_expression", // Kind to find
2978            0,                       // Find the first one
2979        )
2980        .expect("Failed to query for assignment node")
2981        .expect("Could not find the assignment_expression node within caller");
2982
2983        // Navigate: assignment -> right: expression -> child(0): call_expression
2984        let outer_call_expr_node = assignment_node
2985            .child_by_field_name("right")
2986            .expect("Assignment node missing 'right' child")
2987            .child(0)
2988            .expect("Assignment 'right' expression missing child(0)");
2989
2990        assert_eq!(
2991            outer_call_expr_node.kind(),
2992            "call_expression",
2993            "Navigated node is not a call_expression"
2994        );
2995        trace!(
2996            "DEBUG [Test]: Analyzing node kind='{}', text='{}'",
2997            outer_call_expr_node.kind(),
2998            get_node_text(&outer_call_expr_node, source)
2999        );
3000
3001        let steps = analyze_chained_call(
3002            outer_call_expr_node, // Use the navigated node
3003            caller_node_id,
3004            &Some("Test".to_string()),
3005            &ctx,
3006            &graph,
3007            source,
3008            &lang,
3009            &input,
3010            None,
3011            outer_call_expr_node.start_byte(), // Add originating_span_start
3012        )?;
3013
3014        // Should have two steps: add, then sub
3015        assert_eq!(steps.len(), 2);
3016
3017        // Step 1: add
3018        let step1 = &steps[0];
3019        assert_eq!(step1.object_type, Some("uint".to_string())); // value is declared as uint
3020        assert_eq!(step1.result_type, Some("uint".to_string())); // add returns uint
3021        assert_eq!(step1.arguments, vec!["x".to_string()]);
3022        match &step1.target {
3023            ResolvedTarget::Function {
3024                contract_name,
3025                function_name,
3026                ..
3027            } => {
3028                assert_eq!(contract_name.as_deref(), Some("SafeMath"));
3029                assert_eq!(function_name, "add");
3030            }
3031            _ => panic!("Step 1: Expected Library Function, got {:?}", step1.target),
3032        }
3033
3034        // Step 2: sub
3035        let step2 = &steps[1];
3036        assert_eq!(step2.object_type, Some("uint".to_string())); // Object is the result of add (uint)
3037        assert_eq!(step2.result_type, Some("uint".to_string())); // sub returns uint
3038        assert_eq!(step2.arguments, vec!["y".to_string()]);
3039        match &step2.target {
3040            ResolvedTarget::Function {
3041                contract_name,
3042                function_name,
3043                ..
3044            } => {
3045                assert_eq!(contract_name.as_deref(), Some("SafeMath"));
3046                assert_eq!(function_name, "sub");
3047            }
3048            _ => panic!("Step 2: Expected Library Function, got {:?}", step2.target),
3049        }
3050
3051        Ok(())
3052    }
3053
3054    #[test]
3055    fn test_analyze_chained_call_interface_factory() -> Result<()> {
3056        // Use our own Result type
3057        let source = r#"
3058             interface IAction { function perform() external returns (bool); }
3059             contract Action is IAction { function perform() external override returns (bool) { return true; } }
3060             interface IFactory { function create() external returns (IAction); }
3061             contract Factory is IFactory { function create() external override returns (IAction) { return new Action(); } }
3062             contract Test {
3063                 IFactory factory;
3064                 function caller() public returns (bool) {
3065                     // factory.create().perform()
3066                     return factory.create().perform();
3067                 }
3068             }
3069         "#;
3070        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
3071
3072        let _caller_def_node = find_function_definition_node_by_name(&tree, source, &lang, "caller")
3073            .expect("missing function definition");
3074        let caller_node_id = graph
3075            .node_lookup
3076            .get(&(Some("Test".to_string()), "caller".to_string()))
3077            .copied()
3078            .unwrap();
3079
3080        // Find the outer call_expression node for (...).perform()
3081        let call_expr_node = find_nth_node_of_kind(&tree, "call_expression", 1).unwrap(); // Assuming perform is the second call
3082
3083        let steps = analyze_chained_call(
3084            call_expr_node,
3085            caller_node_id,
3086            &Some("Test".to_string()),
3087            &ctx,
3088            &graph,
3089            source,
3090            &lang,
3091            &input,
3092            None,
3093            call_expr_node.start_byte(), // Add originating_span_start
3094        )?;
3095
3096        // Should have two steps: create, then perform
3097        assert_eq!(steps.len(), 2);
3098
3099        // Step 1: create
3100        let step1 = &steps[0];
3101        assert_eq!(step1.object_type, Some("IFactory".to_string())); // factory is IFactory
3102        assert_eq!(step1.result_type, Some("IAction".to_string())); // create returns IAction
3103        assert!(step1.arguments.is_empty());
3104        match &step1.target {
3105            ResolvedTarget::InterfaceMethod {
3106                interface_name,
3107                method_name,
3108                implementation,
3109            } => {
3110                assert_eq!(interface_name, "IFactory");
3111                assert_eq!(method_name, "create");
3112                assert!(implementation.is_some()); // Should resolve to Factory.create
3113                match implementation.as_deref() {
3114                    Some(ResolvedTarget::Function {
3115                        contract_name,
3116                        function_name,
3117                        ..
3118                    }) => {
3119                        assert_eq!(contract_name.as_deref(), Some("Factory"));
3120                        assert_eq!(function_name, "create");
3121                    }
3122                    _ => panic!(
3123                        "Step 1: Expected implementation Function, got {:?}",
3124                        implementation
3125                    ),
3126                }
3127            }
3128            _ => panic!("Step 1: Expected InterfaceMethod, got {:?}", step1.target),
3129        }
3130
3131        // Step 2: perform
3132        let step2 = &steps[1];
3133        assert_eq!(step2.object_type, Some("IAction".to_string())); // Object is the result of create (IAction)
3134        assert_eq!(step2.result_type, Some("bool".to_string())); // perform returns bool
3135        assert!(step2.arguments.is_empty());
3136        match &step2.target {
3137            ResolvedTarget::InterfaceMethod {
3138                interface_name,
3139                method_name,
3140                implementation,
3141            } => {
3142                assert_eq!(interface_name, "IAction");
3143                assert_eq!(method_name, "perform");
3144                assert!(implementation.is_some()); // Should resolve to Action.perform
3145                match implementation.as_deref() {
3146                    Some(ResolvedTarget::Function {
3147                        contract_name,
3148                        function_name,
3149                        ..
3150                    }) => {
3151                        assert_eq!(contract_name.as_deref(), Some("Action"));
3152                        assert_eq!(function_name, "perform");
3153                    }
3154                    _ => panic!(
3155                        "Step 2: Expected implementation Function, got {:?}",
3156                        implementation
3157                    ),
3158                }
3159            }
3160            _ => panic!("Step 2: Expected InterfaceMethod, got {:?}", step2.target),
3161        }
3162
3163        Ok(())
3164    }
3165
3166    #[test]
3167    fn test_analyze_chained_call_inline_factory() -> Result<()> {
3168        // Variant of test_analyze_chained_call_interface_factory
3169        // Here, the factory is instantiated inline: `new Factory().create().perform()`
3170        let source = r#"
3171             interface IAction { function perform() external returns (bool); }
3172             contract Action is IAction { function perform() external override returns (bool) { return true; } }
3173             interface IFactory { function create() external returns (IAction); }
3174             contract Factory is IFactory { function create() external override returns (IAction) { return new Action(); } }
3175             contract Test {
3176                 // No factory state variable
3177                 function caller() public returns (bool) {
3178                     // Instantiate factory inline, then call create, then perform
3179                     return new Factory().create().perform();
3180                 }
3181             }
3182         "#;
3183        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
3184
3185        let _caller_def_node = find_function_definition_node_by_name(&tree, source, &lang, "caller")
3186            .expect("missing function definition");
3187        let caller_node_id = graph
3188            .node_lookup
3189            .get(&(Some("Test".to_string()), "caller".to_string()))
3190            .copied()
3191            .unwrap();
3192
3193        // Find the outermost call_expression node for (...).perform()
3194        // It should be the second call expression overall (first is create, second is perform)
3195        let outer_call_expr_node =
3196            find_nth_node_of_kind(&tree, "call_expression", 1) // Find the second call_expression
3197                .expect("Could not find the outer call_expression node for .perform()");
3198
3199        // Debug: Verify we found the correct node
3200        trace!(
3201            "[Test Inline Factory] Analyzing node kind='{}', text='{}'",
3202            outer_call_expr_node.kind(),
3203            get_node_text(&outer_call_expr_node, source)
3204        );
3205        assert!(get_node_text(&outer_call_expr_node, source).contains(".perform()"));
3206
3207        let steps = analyze_chained_call(
3208            outer_call_expr_node,
3209            caller_node_id,
3210            &Some("Test".to_string()),
3211            &ctx,
3212            &graph,
3213            source,
3214            &lang,
3215            &input,
3216            None,
3217            outer_call_expr_node.start_byte(), // Add originating_span_start
3218        )?;
3219
3220        // Should have three steps: new Factory(), then .create(), then .perform()
3221        assert_eq!(steps.len(), 3, "Expected 3 steps for inline factory chain");
3222
3223        // Step 1: new Factory()
3224        let step1 = &steps[0];
3225        assert_eq!(
3226            step1.object_type, None,
3227            "Step 1 (new): object_type should be None"
3228        );
3229        assert_eq!(
3230            step1.result_type,
3231            Some("Factory".to_string()),
3232            "Step 1 (new): result_type should be Factory"
3233        );
3234        assert!(
3235            step1.arguments.is_empty(),
3236            "Step 1 (new): arguments should be empty"
3237        );
3238        match &step1.target {
3239            ResolvedTarget::Function {
3240                contract_name,
3241                function_name,
3242                node_type,
3243            } => {
3244                assert_eq!(
3245                    contract_name.as_deref(),
3246                    Some("Factory"),
3247                    "Step 1 (new): target contract"
3248                );
3249                assert_eq!(
3250                    function_name, "Factory",
3251                    "Step 1 (new): target function name (constructor)"
3252                );
3253                assert_eq!(
3254                    *node_type,
3255                    NodeType::Constructor,
3256                    "Step 1 (new): target node type"
3257                );
3258            }
3259            _ => panic!(
3260                "Step 1 (new): Expected Function (Constructor), got {:?}",
3261                step1.target
3262            ),
3263        }
3264
3265        // Step 2: .create()
3266        let step2 = &steps[1];
3267        assert_eq!(
3268            step2.object_type,
3269            Some("Factory".to_string()),
3270            "Step 2 (create): object_type should be Factory"
3271        );
3272        assert_eq!(
3273            step2.result_type,
3274            Some("IAction".to_string()),
3275            "Step 2 (create): result_type should be IAction"
3276        );
3277        assert!(
3278            step2.arguments.is_empty(),
3279            "Step 2 (create): arguments should be empty"
3280        );
3281        match &step2.target {
3282            // Assuming resolution finds the concrete implementation directly or via InterfaceMethod
3283            ResolvedTarget::InterfaceMethod {
3284                interface_name,
3285                method_name,
3286                implementation,
3287            } => {
3288                assert_eq!(
3289                    interface_name, "IFactory",
3290                    "Step 2 (create): target interface name"
3291                );
3292                assert_eq!(method_name, "create", "Step 2 (create): target method name");
3293                assert!(
3294                    implementation.is_some(),
3295                    "Step 2 (create): implementation should be resolved"
3296                );
3297                match implementation.as_deref() {
3298                    Some(ResolvedTarget::Function {
3299                        contract_name,
3300                        function_name,
3301                        node_type,
3302                    }) => {
3303                        assert_eq!(
3304                            contract_name.as_deref(),
3305                            Some("Factory"),
3306                            "Step 2 (create): impl contract"
3307                        );
3308                        assert_eq!(
3309                            function_name, "create",
3310                            "Step 2 (create): impl function name"
3311                        );
3312                        assert_eq!(
3313                            *node_type,
3314                            NodeType::Function,
3315                            "Step 2 (create): impl node type"
3316                        );
3317                    }
3318                    _ => panic!(
3319                        "Step 2 (create): Expected implementation Function, got {:?}",
3320                        implementation
3321                    ),
3322                }
3323            }
3324            ResolvedTarget::Function {
3325                contract_name,
3326                function_name,
3327                node_type,
3328            } => {
3329                // Allow direct resolution to function if InterfaceMethod is skipped
3330                assert_eq!(
3331                    contract_name.as_deref(),
3332                    Some("Factory"),
3333                    "Step 2 (create): target contract (direct)"
3334                );
3335                assert_eq!(
3336                    function_name, "create",
3337                    "Step 2 (create): target function name (direct)"
3338                );
3339                assert_eq!(
3340                    *node_type,
3341                    NodeType::Function,
3342                    "Step 2 (create): target node type (direct)"
3343                );
3344            }
3345            _ => panic!(
3346                "Step 2 (create): Expected InterfaceMethod or Function, got {:?}",
3347                step2.target
3348            ),
3349        }
3350
3351        // Step 3: .perform()
3352        let step3 = &steps[2];
3353        assert_eq!(
3354            step3.object_type,
3355            Some("IAction".to_string()),
3356            "Step 3 (perform): object_type should be IAction"
3357        );
3358        assert_eq!(
3359            step3.result_type,
3360            Some("bool".to_string()),
3361            "Step 3 (perform): result_type should be bool"
3362        );
3363        assert!(
3364            step3.arguments.is_empty(),
3365            "Step 3 (perform): arguments should be empty"
3366        );
3367        match &step3.target {
3368            ResolvedTarget::InterfaceMethod {
3369                interface_name,
3370                method_name,
3371                implementation,
3372            } => {
3373                assert_eq!(
3374                    interface_name, "IAction",
3375                    "Step 3 (perform): target interface name"
3376                );
3377                assert_eq!(
3378                    method_name, "perform",
3379                    "Step 3 (perform): target method name"
3380                );
3381                assert!(
3382                    implementation.is_some(),
3383                    "Step 3 (perform): implementation should be resolved"
3384                );
3385                match implementation.as_deref() {
3386                    Some(ResolvedTarget::Function {
3387                        contract_name,
3388                        function_name,
3389                        node_type,
3390                    }) => {
3391                        assert_eq!(
3392                            contract_name.as_deref(),
3393                            Some("Action"),
3394                            "Step 3 (perform): impl contract"
3395                        );
3396                        assert_eq!(
3397                            function_name, "perform",
3398                            "Step 3 (perform): impl function name"
3399                        );
3400                        assert_eq!(
3401                            *node_type,
3402                            NodeType::Function,
3403                            "Step 3 (perform): impl node type"
3404                        );
3405                    }
3406                    _ => panic!(
3407                        "Step 3 (perform): Expected implementation Function, got {:?}",
3408                        implementation
3409                    ),
3410                }
3411            }
3412            _ => panic!(
3413                "Step 3 (perform): Expected InterfaceMethod, got {:?}",
3414                step3.target
3415            ),
3416        }
3417
3418        Ok(())
3419    }
3420
3421    #[test]
3422    fn test_analyze_constructor_call() -> Result<()> {
3423        // Use our own Result type
3424        let source = r#"
3425             contract Target { constructor(uint x) {} }
3426             contract Test {
3427                 function caller() public {
3428                     new Target(123); // Constructor call
3429                 }
3430             }
3431         "#;
3432        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
3433
3434        let caller_def_node = // Prefixed
3435            find_function_definition_node_by_name(&tree, source, &lang, "caller")?; // Use ?
3436        let caller_node_id = graph
3437            .node_lookup
3438            .get(&(Some("Test".to_string()), "caller".to_string()))
3439            .copied()
3440            .ok_or_else(|| anyhow::anyhow!("Caller node ID not found in graph"))?; // Use ok_or_else and ?
3441
3442        // Find the call_expression node wrapping the new_expression within the caller function
3443        // Re-fetch caller_def_node as it was prefixed with _
3444        let caller_def_node_for_search =
3445            find_function_definition_node_by_name(&tree, source, &lang, "caller")?;
3446        let call_expr_node = find_nth_descendant_node_of_kind(
3447                &caller_def_node_for_search, // Start search within the caller function node
3448                source,
3449                &lang,
3450                "call_expression", // Kind to find
3451                0,                 // Find the first one (index 0)
3452            )? // Propagate Result error
3453            .ok_or_else(|| { // Convert Option to Result
3454                // Use our own error type for better context if node not found
3455                TypeError::Internal(format!(
3456                    "Could not find the call_expression node for new Target(123) within caller function node: {:?}",
3457                    caller_def_node.byte_range()
3458                ))
3459            })?; // Propagate Option error
3460
3461        // Optional: Add a debug print to verify the found node
3462        // trace!("DEBUG [Test Constructor Call]: Found call_expr_node: kind='{}', text='{}'",
3463        //           call_expr_node.kind(), get_node_text(&call_expr_node, source));
3464
3465        let steps = analyze_chained_call(
3466            call_expr_node, // Use the node found within the caller function
3467            caller_node_id,
3468            &Some("Test".to_string()),
3469            &ctx,
3470            &graph,
3471            source,
3472            &lang,
3473            &input,
3474            None,
3475            call_expr_node.start_byte(), // Add originating_span_start
3476        )?;
3477
3478        assert_eq!(steps.len(), 1);
3479        let step = &steps[0];
3480        assert_eq!(step.object_type, None);
3481        assert_eq!(step.result_type, Some("Target".to_string())); // Result is the contract type
3482        assert_eq!(step.arguments, vec!["123".to_string()]);
3483        match &step.target {
3484            ResolvedTarget::Function {
3485                contract_name,
3486                function_name,
3487                node_type,
3488            } => {
3489                assert_eq!(contract_name.as_deref(), Some("Target"));
3490                assert_eq!(function_name, "Target"); // Constructor name
3491                assert_eq!(*node_type, NodeType::Constructor);
3492            }
3493            _ => panic!(
3494                "Expected ResolvedTarget::Function (Constructor), got {:?}",
3495                step.target
3496            ),
3497        }
3498
3499        Ok(())
3500    }
3501
3502    #[test]
3503    fn test_analyze_type_cast_call() -> Result<()> {
3504        // Use our own Result type
3505        let source = r#"
3506             interface IAction { function perform() external; }
3507             contract Test {
3508                 address actionAddr;
3509                 function caller() public {
3510                     IAction(actionAddr).perform(); // Type cast then call
3511                 }
3512             }
3513         "#;
3514        let (ctx, graph, tree, lang, input) = setup_test_environment(source)?;
3515        let _caller_def_node = find_function_definition_node_by_name(&tree, source, &lang, "caller")
3516            .expect("missing function definition");
3517        let caller_node_id = graph
3518            .node_lookup
3519            .get(&(Some("Test".to_string()), "caller".to_string()))
3520            .copied()
3521            .unwrap();
3522
3523        // Find the outer call_expression node for (...).perform()
3524        let call_expr_node = find_nth_node_of_kind(&tree, "call_expression", 0).unwrap(); // Only one call expr
3525
3526        let steps = analyze_chained_call(
3527            call_expr_node,
3528            caller_node_id,
3529            &Some("Test".to_string()),
3530            &ctx,
3531            &graph,
3532            source,
3533            &lang,
3534            &input,
3535            None,
3536            call_expr_node.start_byte(), // Add originating_span_start
3537        )?;
3538
3539        // Should have one step: perform
3540        // The type cast `IAction(actionAddr)` itself doesn't create a step, but resolves the type.
3541        assert_eq!(steps.len(), 1);
3542
3543        // Step 1: perform
3544        let step1 = &steps[0];
3545        assert_eq!(step1.object_type, Some("IAction".to_string())); // Object type resolved via cast
3546        assert_eq!(step1.result_type, None); // perform returns nothing
3547        assert!(step1.arguments.is_empty());
3548        match &step1.target {
3549            ResolvedTarget::InterfaceMethod {
3550                interface_name,
3551                method_name,
3552                implementation,
3553            } => {
3554                assert_eq!(interface_name, "IAction");
3555                assert_eq!(method_name, "perform");
3556                // Implementation is None because we don't have a concrete contract provided here
3557                assert!(implementation.is_none());
3558            }
3559            _ => panic!("Step 1: Expected InterfaceMethod, got {:?}", step1.target),
3560        }
3561
3562        Ok(())
3563    }
3564
3565    fn default_natspec_text_range() -> NatspecTextRange {
3566        NatspecTextRange {
3567            start: NatspecTextIndex {
3568                utf8: 0,
3569                line: 0,
3570                column: 0,
3571            },
3572            end: NatspecTextIndex {
3573                utf8: 0,
3574                line: 0,
3575                column: 0,
3576            },
3577        }
3578    }
3579
3580    #[test]
3581    fn test_analyze_interface_call_fallback_to_binding_multiple_implementers() -> Result<()> {
3582        let source = r#"
3583            interface IMyService { function doThing() external returns (uint); }
3584            contract MyServiceImplA is IMyService { function doThing() external override returns (uint) { return 1; } }
3585            contract MyServiceImplB is IMyService { function doThing() external override returns (uint) { return 2; } }
3586
3587            contract Caller {
3588                IMyService serviceInstance;
3589                function callIt() public returns (uint) {
3590                    return serviceInstance.doThing();
3591                }
3592            }
3593        "#;
3594        let (mut ctx, graph, tree, lang, input) = setup_test_environment(source)?;
3595
3596        // Create Manifest
3597        let manifest_entry = ManifestEntry {
3598            file_path: std::path::PathBuf::from("test.sol"),
3599            text: "/// @custom:binds-to MyService_BoundTo_A".to_string(),
3600            raw_comment_span: default_natspec_text_range(),
3601            item_kind: NatspecSourceItemKind::Interface,
3602            item_name: Some("IMyService".to_string()),
3603            item_span: default_natspec_text_range(), // Actual span not critical for this test's logic path
3604            is_natspec: true,
3605        };
3606        let manifest = Manifest {
3607            entries: vec![manifest_entry],
3608        };
3609        ctx.manifest = Some(manifest);
3610
3611        // Create BindingRegistry
3612        let binding_config = BindingConfig {
3613            key: "MyService_BoundTo_A".to_string(),
3614            contract_name: Some("MyServiceImplA".to_string()),
3615            address: None,
3616            chain_id: None,
3617            notes: None,
3618        };
3619        let mut bindings_map = HashMap::new();
3620        bindings_map.insert("MyService_BoundTo_A".to_string(), binding_config);
3621        let binding_registry = BindingRegistry {
3622            bindings: bindings_map,
3623        };
3624        ctx.binding_registry = Some(binding_registry);
3625
3626        // Find the 'caller' function definition node
3627        let caller_func_def_node =
3628            find_function_definition_node_by_name(&tree, source, &lang, "callIt")
3629                .expect("Could not find function definition node for callIt");
3630
3631        // Get the Node ID for the caller function from the graph
3632        let caller_node_id = graph
3633            .node_lookup
3634            .get(&(Some("Caller".to_string()), "callIt".to_string()))
3635            .copied()
3636            .ok_or_else(|| anyhow::anyhow!("Node ID for Caller.callIt not found in graph"))?;
3637
3638        // Find the call_expression node for serviceInstance.doThing()
3639        let call_expr_node = find_nth_descendant_node_of_kind(
3640            &caller_func_def_node,
3641            source,
3642            &lang,
3643            "call_expression",
3644            0, // First call expression in callIt
3645        )?
3646        .ok_or_else(|| {
3647            TypeError::Internal(
3648                "call_expression node for serviceInstance.doThing() not found".to_string(),
3649            )
3650        })?;
3651
3652        let steps = analyze_chained_call(
3653            call_expr_node,
3654            caller_node_id,
3655            &Some("Caller".to_string()),
3656            &ctx,
3657            &graph,
3658            source,
3659            &lang,
3660            &input,
3661            None,
3662            call_expr_node.start_byte(),
3663        )?;
3664
3665        assert_eq!(steps.len(), 1, "Expected one step for the call");
3666        let step = &steps[0];
3667
3668        assert_eq!(step.object_type, Some("IMyService".to_string()));
3669        assert_eq!(step.result_type, Some("uint".to_string())); // MyServiceImplA.doThing returns uint
3670
3671        match &step.target {
3672            ResolvedTarget::InterfaceMethod {
3673                interface_name,
3674                method_name,
3675                implementation,
3676            } => {
3677                assert_eq!(interface_name, "IMyService");
3678                assert_eq!(method_name, "doThing");
3679                assert!(
3680                    implementation.is_some(),
3681                    "Implementation should be resolved via binding"
3682                );
3683
3684                match implementation.as_deref() {
3685                    Some(ResolvedTarget::Function {
3686                        contract_name,
3687                        function_name,
3688                        node_type,
3689                    }) => {
3690                        assert_eq!(contract_name.as_deref(), Some("MyServiceImplA"));
3691                        assert_eq!(function_name, "doThing");
3692                        assert_eq!(*node_type, NodeType::Function);
3693                    }
3694                    _ => panic!(
3695                        "Expected implementation to be ResolvedTarget::Function, got {:?}",
3696                        implementation
3697                    ),
3698                }
3699            }
3700            _ => panic!(
3701                "Expected ResolvedTarget::InterfaceMethod, got {:?}",
3702                step.target
3703            ),
3704        }
3705        Ok(())
3706    }
3707
3708    #[test]
3709    fn test_analyze_interface_call_fallback_to_binding_zero_implementers() -> Result<()> {
3710        let source = r#"
3711            interface IExternalService { function performRemoteAction() external returns (bool); }
3712            // No direct implementers of IExternalService in this source
3713
3714            // This contract will be specified in bindings
3715            contract BoundRemoteImpl {
3716                function performRemoteAction() public pure returns (bool) { return true; }
3717            }
3718
3719            contract UserContract {
3720                IExternalService remoteService;
3721                function executeRemote() public returns (bool) {
3722                    return remoteService.performRemoteAction();
3723                }
3724            }
3725        "#;
3726        let (mut ctx, graph, tree, lang, input) = setup_test_environment(source)?;
3727
3728        // Create Manifest
3729        let manifest_entry = ManifestEntry {
3730            file_path: std::path::PathBuf::from("interfaces.sol"), // Dummy path
3731            text: "/// @custom:binds-to RemoteServiceKey".to_string(),
3732            raw_comment_span: default_natspec_text_range(),
3733            item_kind: NatspecSourceItemKind::Interface,
3734            item_name: Some("IExternalService".to_string()),
3735            item_span: default_natspec_text_range(),
3736            is_natspec: true,
3737        };
3738        let manifest = Manifest {
3739            entries: vec![manifest_entry],
3740        };
3741        ctx.manifest = Some(manifest);
3742
3743        // Create BindingRegistry
3744        let binding_config = BindingConfig {
3745            key: "RemoteServiceKey".to_string(),
3746            contract_name: Some("BoundRemoteImpl".to_string()),
3747            address: None,
3748            chain_id: None,
3749            notes: None,
3750        };
3751        let mut bindings_map = HashMap::new();
3752        bindings_map.insert("RemoteServiceKey".to_string(), binding_config);
3753        let binding_registry = BindingRegistry {
3754            bindings: bindings_map,
3755        };
3756        ctx.binding_registry = Some(binding_registry);
3757
3758        let caller_func_def_node =
3759            find_function_definition_node_by_name(&tree, source, &lang, "executeRemote")?;
3760        let caller_node_id = graph
3761            .node_lookup
3762            .get(&(
3763                Some("UserContract".to_string()),
3764                "executeRemote".to_string(),
3765            ))
3766            .copied()
3767            .ok_or_else(|| anyhow::anyhow!("Node ID for UserContract.executeRemote not found"))?;
3768
3769        let call_expr_node = find_nth_descendant_node_of_kind(
3770            &caller_func_def_node,
3771            source,
3772            &lang,
3773            "call_expression",
3774            0,
3775        )?
3776        .ok_or_else(|| {
3777            TypeError::Internal(
3778                "call_expression node for remoteService.performRemoteAction() not found"
3779                    .to_string(),
3780            )
3781        })?;
3782
3783        let steps = analyze_chained_call(
3784            call_expr_node,
3785            caller_node_id,
3786            &Some("UserContract".to_string()),
3787            &ctx,
3788            &graph,
3789            source,
3790            &lang,
3791            &input,
3792            None,
3793            call_expr_node.start_byte(),
3794        )?;
3795
3796        assert_eq!(steps.len(), 1);
3797        let step = &steps[0];
3798        assert_eq!(step.object_type, Some("IExternalService".to_string()));
3799        assert_eq!(step.result_type, Some("bool".to_string())); // BoundRemoteImpl.performRemoteAction returns bool
3800
3801        match &step.target {
3802            ResolvedTarget::InterfaceMethod {
3803                interface_name,
3804                method_name,
3805                implementation,
3806            } => {
3807                assert_eq!(interface_name, "IExternalService");
3808                assert_eq!(method_name, "performRemoteAction");
3809                assert!(
3810                    implementation.is_some(),
3811                    "Implementation should be resolved via binding"
3812                );
3813
3814                match implementation.as_deref() {
3815                    Some(ResolvedTarget::Function {
3816                        contract_name,
3817                        function_name,
3818                        node_type,
3819                    }) => {
3820                        assert_eq!(contract_name.as_deref(), Some("BoundRemoteImpl"));
3821                        assert_eq!(function_name, "performRemoteAction");
3822                        assert_eq!(*node_type, NodeType::Function);
3823                    }
3824                    _ => panic!(
3825                        "Expected implementation to be ResolvedTarget::Function, got {:?}",
3826                        implementation
3827                    ),
3828                }
3829            }
3830            _ => panic!(
3831                "Expected ResolvedTarget::InterfaceMethod, got {:?}",
3832                step.target
3833            ),
3834        }
3835        Ok(())
3836    }
3837
3838    #[test]
3839    fn test_analyze_interface_call_structural_typing_via_shared_key_no_inheritance() -> Result<()> {
3840        let source = r#"
3841            interface IExternal {
3842                /// @custom:binds-to ExternalKey
3843                function doExternalWork() external returns (string);
3844            }
3845
3846            /// @custom:binds-to ExternalKey
3847            contract ExternalContractImpl {
3848                // This contract does NOT explicitly implement IExternal
3849                function doExternalWork() public pure returns (string) {
3850                    return "ExternalContractImpl.doExternalWork";
3851                }
3852            }
3853
3854            contract CallerContract {
3855                IExternal externalService;
3856
3857                function setService(address _serviceAddr) public {
3858                    externalService = IExternal(_serviceAddr);
3859                }
3860
3861                function callExternalWork() public returns (string) {
3862                    return externalService.doExternalWork(); // This should resolve to ExternalContractImpl.doExternalWork
3863                }
3864            }
3865        "#;
3866
3867        // --- Setup Manifest ---
3868        let mut manifest_entries = Vec::new();
3869
3870        // ManifestEntry for IExternal
3871        manifest_entries.push(ManifestEntry {
3872            file_path: std::path::PathBuf::from("test.sol"),
3873            text: "/// @custom:binds-to ExternalKey".to_string(),
3874            raw_comment_span: default_natspec_text_range(), // Dummy span
3875            item_kind: NatspecSourceItemKind::Interface,
3876            item_name: Some("IExternal".to_string()),
3877            item_span: NatspecTextRange {
3878                // Approx span for "interface IExternal"
3879                start: NatspecTextIndex {
3880                    utf8: 13,
3881                    line: 1,
3882                    column: 12,
3883                },
3884                end: NatspecTextIndex {
3885                    utf8: 108,
3886                    line: 4,
3887                    column: 13,
3888                },
3889            },
3890            is_natspec: true,
3891        });
3892
3893        // ManifestEntry for ExternalContractImpl
3894        manifest_entries.push(ManifestEntry {
3895            file_path: std::path::PathBuf::from("test.sol"),
3896            text: "/// @custom:binds-to ExternalKey".to_string(),
3897            raw_comment_span: default_natspec_text_range(), // Dummy span
3898            item_kind: NatspecSourceItemKind::Contract,
3899            item_name: Some("ExternalContractImpl".to_string()),
3900            item_span: NatspecTextRange {
3901                // Approx span for "contract ExternalContractImpl"
3902                start: NatspecTextIndex {
3903                    utf8: 123,
3904                    line: 6,
3905                    column: 0,
3906                },
3907                end: NatspecTextIndex {
3908                    utf8: 280,
3909                    line: 11,
3910                    column: 1,
3911                },
3912            },
3913            is_natspec: true,
3914        });
3915
3916        // ManifestEntry for CallerContract.callExternalWork (to ensure it's in the graph)
3917        manifest_entries.push(ManifestEntry {
3918            file_path: std::path::PathBuf::from("test.sol"),
3919            text: "".to_string(), // No natspec needed for the caller function itself for this test
3920            raw_comment_span: default_natspec_text_range(),
3921            item_kind: NatspecSourceItemKind::Function,
3922            item_name: Some("callExternalWork".to_string()),
3923            item_span: NatspecTextRange {
3924                // Approx span for "function callExternalWork"
3925                start: NatspecTextIndex {
3926                    utf8: 408,
3927                    line: 19,
3928                    column: 4,
3929                },
3930                end: NatspecTextIndex {
3931                    utf8: 504,
3932                    line: 21,
3933                    column: 5,
3934                },
3935            },
3936            is_natspec: false,
3937        });
3938
3939        let manifest = Manifest {
3940            entries: manifest_entries,
3941        };
3942        let mut binding_registry = BindingRegistry::default();
3943
3944        // Simulate sol2cg's population of the registry from contract Natspec
3945        binding_registry.populate_from_manifest(&manifest);
3946
3947        // Verify that populate_from_manifest correctly set the contract_name for ExternalKey
3948        let binding_conf_after_populate = binding_registry.get_binding("ExternalKey");
3949        assert!(
3950            binding_conf_after_populate.is_some(),
3951            "BindingConfig for ExternalKey should exist after populate"
3952        );
3953        assert_eq!(
3954            binding_conf_after_populate.unwrap().contract_name,
3955            Some("ExternalContractImpl".to_string()),
3956            "BindingConfig for ExternalKey should have ExternalContractImpl as contract_name"
3957        );
3958
3959        let (ctx, graph, tree, lang, input) =
3960            setup_test_environment_customized(source, Some(manifest), Some(binding_registry))?;
3961
3962        // Find the 'callExternalWork' function definition node in CallerContract
3963        let caller_func_def_node =
3964            find_function_definition_node_by_name(&tree, source, &lang, "callExternalWork")?;
3965
3966        // Get the Node ID for the caller function from the graph
3967        let caller_node_id = graph
3968            .node_lookup
3969            .get(&(
3970                Some("CallerContract".to_string()),
3971                "callExternalWork".to_string(),
3972            ))
3973            .copied()
3974            .ok_or_else(|| {
3975                anyhow::anyhow!("Node ID for CallerContract.callExternalWork not found")
3976            })?;
3977
3978        // Find the call_expression node for externalService.doExternalWork()
3979        let call_expr_node = find_nth_descendant_node_of_kind(
3980            &caller_func_def_node,
3981            source,
3982            &lang,
3983            "call_expression",
3984            0, // First call expression in callExternalWork
3985        )?
3986        .ok_or_else(|| {
3987            TypeError::Internal(
3988                "call_expression node for externalService.doExternalWork() not found".to_string(),
3989            )
3990        })?;
3991
3992        let steps = analyze_chained_call(
3993            call_expr_node,
3994            caller_node_id,
3995            &Some("CallerContract".to_string()),
3996            &ctx,
3997            &graph,
3998            source,
3999            &lang,
4000            &input,
4001            None, // This is the top-level call being analyzed
4002            call_expr_node.start_byte(),
4003        )?;
4004
4005        assert_eq!(steps.len(), 1, "Expected one step for the call");
4006        let step = &steps[0];
4007
4008        assert_eq!(
4009            step.object_type,
4010            Some("IExternal".to_string()),
4011            "Object type should be IExternal"
4012        );
4013        assert_eq!(
4014            step.result_type,
4015            Some("string".to_string()),
4016            "Result type should be string (from ExternalContractImpl.doExternalWork)"
4017        );
4018
4019        match &step.target {
4020            ResolvedTarget::InterfaceMethod {
4021                interface_name,
4022                method_name,
4023                implementation,
4024            } => {
4025                assert_eq!(interface_name, "IExternal");
4026                assert_eq!(method_name, "doExternalWork");
4027                assert!(
4028                    implementation.is_some(),
4029                    "Implementation should be resolved via shared Natspec binding key"
4030                );
4031
4032                match implementation.as_deref() {
4033                    Some(ResolvedTarget::Function {
4034                        contract_name: impl_contract_name,
4035                        function_name: impl_function_name,
4036                        node_type,
4037                    }) => {
4038                        assert_eq!(
4039                            impl_contract_name.as_deref(),
4040                            Some("ExternalContractImpl"),
4041                            "Implementation should be ExternalContractImpl"
4042                        );
4043                        assert_eq!(impl_function_name, "doExternalWork");
4044                        assert_eq!(*node_type, NodeType::Function);
4045                    }
4046                    _ => panic!(
4047                        "Expected implementation to be ResolvedTarget::Function, got {:?}",
4048                        implementation
4049                    ),
4050                }
4051            }
4052            _ => panic!(
4053                "Expected ResolvedTarget::InterfaceMethod, got {:?}",
4054                step.target
4055            ),
4056        }
4057        Ok(())
4058    }
4059
4060    #[test]
4061    fn test_analyze_interface_cast_call_structural_typing_shared_key() -> Result<()> {
4062        let source = r#"
4063            interface IERC20 {
4064                /// @custom:binds-to ERC20Key
4065                function balanceOf(address account) external view returns (uint256);
4066            }
4067
4068            /// @custom:binds-to ERC20Key
4069            contract MyToken {
4070                // This contract does NOT explicitly implement IERC20
4071                mapping(address => uint256) private _balances;
4072
4073                function balanceOf(address account) public view returns (uint256) {
4074                    return _balances[account];
4075                }
4076            }
4077
4078            contract CallerContract {
4079                address token0; // Assume this is set to MyToken's address
4080
4081                constructor(address _token0) {
4082                    token0 = _token0;
4083                }
4084
4085                function getMyBalance() public view returns (uint256) {
4086                    // Call via type cast: InterfaceName(variable).method()
4087                    return IERC20(token0).balanceOf(address(this));
4088                }
4089            }
4090        "#;
4091
4092        // --- Setup Manifest ---
4093        let mut manifest_entries = Vec::new();
4094
4095        manifest_entries.push(ManifestEntry {
4096            file_path: std::path::PathBuf::from("test.sol"),
4097            text: "/// @custom:binds-to ERC20Key".to_string(),
4098            raw_comment_span: default_natspec_text_range(),
4099            item_kind: NatspecSourceItemKind::Interface,
4100            item_name: Some("IERC20".to_string()),
4101            item_span: NatspecTextRange {
4102                // Approx span for "interface IERC20"
4103                start: NatspecTextIndex {
4104                    utf8: 13,
4105                    line: 1,
4106                    column: 12,
4107                },
4108                end: NatspecTextIndex {
4109                    utf8: 123,
4110                    line: 4,
4111                    column: 13,
4112                },
4113            },
4114            is_natspec: true,
4115        });
4116
4117        manifest_entries.push(ManifestEntry {
4118            file_path: std::path::PathBuf::from("test.sol"),
4119            text: "/// @custom:binds-to ERC20Key".to_string(),
4120            raw_comment_span: default_natspec_text_range(),
4121            item_kind: NatspecSourceItemKind::Contract,
4122            item_name: Some("MyToken".to_string()),
4123            item_span: NatspecTextRange {
4124                // Approx span for "contract MyToken"
4125                start: NatspecTextIndex {
4126                    utf8: 138,
4127                    line: 6,
4128                    column: 0,
4129                },
4130                end: NatspecTextIndex {
4131                    utf8: 330,
4132                    line: 13,
4133                    column: 1,
4134                },
4135            },
4136            is_natspec: true,
4137        });
4138
4139        manifest_entries.push(ManifestEntry {
4140            // For CallerContract.getMyBalance
4141            file_path: std::path::PathBuf::from("test.sol"),
4142            text: "".to_string(),
4143            raw_comment_span: default_natspec_text_range(),
4144            item_kind: NatspecSourceItemKind::Function,
4145            item_name: Some("getMyBalance".to_string()),
4146            item_span: NatspecTextRange {
4147                // Approx span for "function getMyBalance"
4148                start: NatspecTextIndex {
4149                    utf8: 450,
4150                    line: 20,
4151                    column: 4,
4152                },
4153                end: NatspecTextIndex {
4154                    utf8: 590,
4155                    line: 24,
4156                    column: 5,
4157                },
4158            },
4159            is_natspec: false,
4160        });
4161
4162        let manifest = Manifest {
4163            entries: manifest_entries,
4164        };
4165        let mut binding_registry = BindingRegistry::default();
4166        binding_registry.populate_from_manifest(&manifest);
4167
4168        // Verify binding population
4169        let binding_conf = binding_registry.get_binding("ERC20Key");
4170        assert!(
4171            binding_conf.is_some(),
4172            "BindingConfig for ERC20Key should exist"
4173        );
4174        assert_eq!(
4175            binding_conf.unwrap().contract_name,
4176            Some("MyToken".to_string()),
4177            "ERC20Key should bind to MyToken contract"
4178        );
4179
4180        let (ctx, graph, tree, lang, input) =
4181            setup_test_environment_customized(source, Some(manifest), Some(binding_registry))?;
4182
4183        let caller_func_def_node =
4184            find_function_definition_node_by_name(&tree, source, &lang, "getMyBalance")?;
4185        let caller_node_id = graph
4186            .node_lookup
4187            .get(&(
4188                Some("CallerContract".to_string()),
4189                "getMyBalance".to_string(),
4190            ))
4191            .copied()
4192            .ok_or_else(|| anyhow::anyhow!("Node ID for CallerContract.getMyBalance not found"))?;
4193
4194        // Find the call_expression node for IERC20(token0).balanceOf(address(this))
4195        // This is the only call_expression within getMyBalance.
4196        let call_expr_node = find_nth_descendant_node_of_kind(
4197            &caller_func_def_node,
4198            source,
4199            &lang,
4200            "call_expression",
4201            0,
4202        )?
4203        .ok_or_else(|| {
4204            TypeError::Internal(
4205                "call_expression node for IERC20(token0).balanceOf(...) not found".to_string(),
4206            )
4207        })?;
4208
4209        let steps = analyze_chained_call(
4210            call_expr_node,
4211            caller_node_id,
4212            &Some("CallerContract".to_string()),
4213            &ctx,
4214            &graph,
4215            source,
4216            &lang,
4217            &input,
4218            None,
4219            call_expr_node.start_byte(),
4220        )?;
4221
4222        assert_eq!(steps.len(), 1, "Expected one step for the call");
4223        let step = &steps[0];
4224
4225        assert_eq!(
4226            step.object_type,
4227            Some("IERC20".to_string()),
4228            "Object type should be IERC20 (from type cast)"
4229        );
4230        assert_eq!(
4231            step.result_type,
4232            Some("uint256".to_string()),
4233            "Result type should be uint256 (from MyToken.balanceOf)"
4234        );
4235        assert_eq!(step.arguments.len(), 1, "Expected one argument");
4236        assert_eq!(
4237            step.arguments[0], "address(this)",
4238            "Argument should be address(this)"
4239        );
4240
4241        match &step.target {
4242            ResolvedTarget::InterfaceMethod {
4243                interface_name,
4244                method_name,
4245                implementation,
4246            } => {
4247                assert_eq!(interface_name, "IERC20");
4248                assert_eq!(method_name, "balanceOf");
4249                assert!(
4250                    implementation.is_some(),
4251                    "Implementation should be resolved via shared Natspec binding key"
4252                );
4253
4254                match implementation.as_deref() {
4255                    Some(ResolvedTarget::Function {
4256                        contract_name: impl_contract_name,
4257                        function_name: impl_function_name,
4258                        node_type,
4259                    }) => {
4260                        assert_eq!(
4261                            impl_contract_name.as_deref(),
4262                            Some("MyToken"),
4263                            "Implementation should be MyToken"
4264                        );
4265                        assert_eq!(impl_function_name, "balanceOf");
4266                        assert_eq!(*node_type, NodeType::Function);
4267                    }
4268                    _ => panic!(
4269                        "Expected implementation to be ResolvedTarget::Function, got {:?}",
4270                        implementation
4271                    ),
4272                }
4273            }
4274            _ => panic!(
4275                "Expected ResolvedTarget::InterfaceMethod, got {:?}",
4276                step.target
4277            ),
4278        }
4279        Ok(())
4280    }
4281
4282    #[test]
4283    fn test_analyze_call_on_interface_method_to_mapping_getter_with_binding() -> Result<()> {
4284        let source = r#"
4285            interface IUniswapV2ERC20 {
4286                /// @custom:binds-to ERC20Impl
4287                function balanceOf(address account) external view returns (uint);
4288            }
4289
4290            /// @custom:binds-to ERC20Impl
4291            contract UniswapV2ERC20 { // Does not explicitly inherit
4292                mapping(address => uint) public balanceOf;
4293                // Other UniswapV2ERC20 members not needed for this specific test
4294            }
4295
4296            contract CallerContract {
4297                address token; // Changed from IUniswapV2ERC20 to address
4298
4299                constructor(address _tokenAddress) {
4300                    token = _tokenAddress; // Store the address directly
4301                }
4302
4303                function getBalance(address who) public view returns (uint) {
4304                    // Instantiate interface and call in one line
4305                    return IUniswapV2ERC20(token).balanceOf(who);
4306                }
4307            }
4308        "#;
4309
4310        // --- Setup Manifest & BindingRegistry ---
4311        let mut manifest_entries = Vec::new();
4312        manifest_entries.push(ManifestEntry {
4313            file_path: std::path::PathBuf::from("test.sol"),
4314            text: "/// @custom:binds-to ERC20Impl".to_string(),
4315            raw_comment_span: default_natspec_text_range(),
4316            item_kind: NatspecSourceItemKind::Interface,
4317            item_name: Some("IUniswapV2ERC20".to_string()),
4318            item_span: default_natspec_text_range(),
4319            is_natspec: true,
4320        });
4321        manifest_entries.push(ManifestEntry {
4322            file_path: std::path::PathBuf::from("test.sol"),
4323            text: "/// @custom:binds-to ERC20Impl".to_string(),
4324            raw_comment_span: default_natspec_text_range(),
4325            item_kind: NatspecSourceItemKind::Contract,
4326            item_name: Some("UniswapV2ERC20".to_string()),
4327            item_span: default_natspec_text_range(),
4328            is_natspec: true,
4329        });
4330        // Add entries for other contracts/functions if needed for graph construction
4331        manifest_entries.push(ManifestEntry {
4332            file_path: std::path::PathBuf::from("test.sol"),
4333            text: "".to_string(),
4334            raw_comment_span: default_natspec_text_range(),
4335            item_kind: NatspecSourceItemKind::Contract,
4336            item_name: Some("CallerContract".to_string()),
4337            item_span: default_natspec_text_range(),
4338            is_natspec: false,
4339        });
4340        manifest_entries.push(ManifestEntry {
4341            file_path: std::path::PathBuf::from("test.sol"),
4342            text: "".to_string(),
4343            raw_comment_span: default_natspec_text_range(),
4344            item_kind: NatspecSourceItemKind::Function,
4345            item_name: Some("getBalance".to_string()),
4346            item_span: default_natspec_text_range(),
4347            is_natspec: false,
4348        });
4349
4350        let manifest = Manifest {
4351            entries: manifest_entries,
4352        };
4353        let mut binding_registry = BindingRegistry::default();
4354        binding_registry.populate_from_manifest(&manifest);
4355
4356        let (ctx, graph, tree, lang, input) =
4357            setup_test_environment_customized(source, Some(manifest), Some(binding_registry))?;
4358
4359        let caller_func_def_node =
4360            find_function_definition_node_by_name(&tree, source, &lang, "getBalance")?;
4361        let caller_node_id = graph
4362            .node_lookup
4363            .get(&(Some("CallerContract".to_string()), "getBalance".to_string()))
4364            .copied()
4365            .ok_or_else(|| anyhow::anyhow!("Node ID for CallerContract.getBalance not found"))?;
4366
4367        // Find the call_expression node for `token.balanceOf(who)`
4368        let call_expr_node = find_nth_descendant_node_of_kind(
4369            &caller_func_def_node,
4370            source,
4371            &lang,
4372            "call_expression",
4373            0, // The first call in getBalance is token.balanceOf(who)
4374        )?
4375        .ok_or_else(|| {
4376            TypeError::Internal(
4377                "Could not find the call_expression for token.balanceOf(who)".to_string(),
4378            )
4379        })?;
4380
4381        let steps = analyze_chained_call(
4382            call_expr_node,
4383            caller_node_id,
4384            &Some("CallerContract".to_string()),
4385            &ctx,
4386            &graph,
4387            source,
4388            &lang,
4389            &input,
4390            None,
4391            call_expr_node.start_byte(),
4392        )?;
4393
4394        assert_eq!(steps.len(), 1, "Expected one step in the chain");
4395
4396        let step1 = &steps[0];
4397        trace!("Step 1: {:?}", step1);
4398        assert_eq!(
4399            step1.object_type,
4400            Some("IUniswapV2ERC20".to_string()),
4401            "Step 1 object_type"
4402        );
4403        assert_eq!(
4404            step1.object_instance_text,
4405            Some("IUniswapV2ERC20(token)".to_string()), // Updated expected object instance text
4406            "Step 1 object_instance_text"
4407        );
4408        assert_eq!(step1.arguments, vec!["who".to_string()], "Step 1 arguments");
4409        assert_eq!(
4410            step1.result_type,
4411            Some("uint".to_string()),
4412            "Step 1 result_type (must be uint from mapping)"
4413        );
4414
4415        match &step1.target {
4416            ResolvedTarget::InterfaceMethod {
4417                interface_name,
4418                method_name,
4419                implementation,
4420            } => {
4421                assert_eq!(
4422                    interface_name, "IUniswapV2ERC20",
4423                    "Step 1 target interface_name"
4424                );
4425                assert_eq!(method_name, "balanceOf", "Step 1 target method_name");
4426                assert!(
4427                    implementation.is_some(),
4428                    "Step 1 implementation should be resolved"
4429                );
4430
4431                match implementation.as_deref() {
4432                    Some(ResolvedTarget::Function { contract_name, function_name, node_type }) => {
4433                        assert_eq!(contract_name.as_deref(), Some("UniswapV2ERC20"), "Step 1 impl contract (concrete contract)");
4434                        assert_eq!(function_name, "balanceOf", "Step 1 impl function (getter for mapping)");
4435                        assert_eq!(*node_type, NodeType::Function, "Step 1 impl type (getter is a function)");
4436                    }
4437                    _ => panic!("Step 1: Expected implementation to be ResolvedTarget::Function for getter, got {:?}", implementation),
4438                }
4439            }
4440            _ => panic!(
4441                "Step 1: Expected ResolvedTarget::InterfaceMethod, got {:?}",
4442                step1.target
4443            ),
4444        }
4445
4446        Ok(())
4447    }
4448}