<commit_before>//! Renderer for function calls.

use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
use ide_db::{SnippetCap, SymbolKind};
use itertools::Itertools;
use stdx::{format_to, to_lower_snake_case};
use syntax::{AstNode, SmolStr};

use crate::{
    context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
    item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
    render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
    CallableSnippets,
};

#[derive(Debug)]
enum FuncKind<'ctx> {
    Function(&'ctx PathCompletionCtx),
    Method(&'ctx DotAccess, Option<hir::Name>),
}

pub(crate) fn render_fn(
    ctx: RenderContext<'_>,
    path_ctx: &PathCompletionCtx,
    local_name: Option<hir::Name>,
    func: hir::Function,
) -> Builder {
    let _p = profile::span("render_fn");
    render(ctx, local_name, func, FuncKind::Function(path_ctx))
}

pub(crate) fn render_method(
    ctx: RenderContext<'_>,
    dot_access: &DotAccess,
    receiver: Option<hir::Name>,
    local_name: Option<hir::Name>,
    func: hir::Function,
) -> Builder {
    let _p = profile::span("render_method");
    render(ctx, local_name, func, FuncKind::Method(dot_access, receiver))
}

fn render(
    ctx @ RenderContext { completion, .. }: RenderContext<'_>,
    local_name: Option<hir::Name>,
    func: hir::Function,
    func_kind: FuncKind<'_>,
) -> Builder {
    let db = completion.db;

    let name = local_name.unwrap_or_else(|| func.name(db));

    let (call, escaped_call) = match &func_kind {
        FuncKind::Method(_, Some(receiver)) => (
            format!("{}.{}", receiver, &name).into(),
            format!("{}.{}", receiver.escaped(), name.escaped()).into(),
        ),
        _ => (name.to_smol_str(), name.escaped().to_smol_str()),
    };
    let mut item = CompletionItem::new(
        if func.self_param(db).is_some() {
            CompletionItemKind::Method
        } else {
            CompletionItemKind::SymbolKind(SymbolKind::Function)
        },
        ctx.source_range(),
        call.clone(),
    );

    let ret_type = func.ret_type(db);
    let is_op_method = func
        .as_assoc_item(ctx.db())
        .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db()))
        .map_or(false, |trait_| completion.is_ops_trait(trait_));
    item.set_relevance(CompletionRelevance {
        type_match: compute_type_match(completion, &ret_type),
        exact_name_match: compute_exact_name_match(completion, &call),
        is_op_method,
        ..ctx.completion_relevance()
    });

    if let Some(ref_match) = compute_ref_match(completion, &ret_type) {
        match func_kind {
            FuncKind::Function(path_ctx) => {
                item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
            }
            FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
                item.ref_match(ref_match, receiver.syntax().text_range().start());
            }
            _ => (),
        }
    }<commit_msg>Find original ast node before compute ref match in fn render<commit_after>//! Renderer for function calls.

use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
use ide_db::{SnippetCap, SymbolKind};
use itertools::Itertools;
use stdx::{format_to, to_lower_snake_case};
use syntax::{AstNode, SmolStr};

use crate::{
    context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
    item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
    render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
    CallableSnippets,
};

#[derive(Debug)]
enum FuncKind<'ctx> {
    Function(&'ctx PathCompletionCtx),
    Method(&'ctx DotAccess, Option<hir::Name>),
}

pub(crate) fn render_fn(
    ctx: RenderContext<'_>,
    path_ctx: &PathCompletionCtx,
    local_name: Option<hir::Name>,
    func: hir::Function,
) -> Builder {
    let _p = profile::span("render_fn");
    render(ctx, local_name, func, FuncKind::Function(path_ctx))
}

pub(crate) fn render_method(
    ctx: RenderContext<'_>,
    dot_access: &DotAccess,
    receiver: Option<hir::Name>,
    local_name: Option<hir::Name>,
    func: hir::Function,
) -> Builder {
    let _p = profile::span("render_method");
    render(ctx, local_name, func, FuncKind::Method(dot_access, receiver))
}

fn render(
    ctx @ RenderContext { completion, .. }: RenderContext<'_>,
    local_name: Option<hir::Name>,
    func: hir::Function,
    func_kind: FuncKind<'_>,
) -> Builder {
    let db = completion.db;

    let name = local_name.unwrap_or_else(|| func.name(db));

    let (call, escaped_call) = match &func_kind {
        FuncKind::Method(_, Some(receiver)) => (
            format!("{}.{}", receiver, &name).into(),
            format!("{}.{}", receiver.escaped(), name.escaped()).into(),
        ),
        _ => (name.to_smol_str(), name.escaped().to_smol_str()),
    };
    let mut item = CompletionItem::new(
        if func.self_param(db).is_some() {
            CompletionItemKind::Method
        } else {
            CompletionItemKind::SymbolKind(SymbolKind::Function)
        },
        ctx.source_range(),
        call.clone(),
    );

    let ret_type = func.ret_type(db);
    let is_op_method = func
        .as_assoc_item(ctx.db())
        .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db()))
        .map_or(false, |trait_| completion.is_ops_trait(trait_));
    item.set_relevance(CompletionRelevance {
        type_match: compute_type_match(completion, &ret_type),
        exact_name_match: compute_exact_name_match(completion, &call),
        is_op_method,
        ..ctx.completion_relevance()
    });

    if let Some(ref_match) = compute_ref_match(completion, &ret_type) {
        match func_kind {
            FuncKind::Function(path_ctx) => {
                item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
            }
            FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
                if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) {
                    item.ref_match(ref_match, original_expr.syntax().text_range().start());
                }
            }
            _ => (),
        }
    }
{'fraction_non_alphanumeric': 0.14904074837482162, 'fraction_numerical': 0.0, 'mean_word_length': 3.3473466574776016, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3621195', 'n_tokens_mistral': 2058, 'n_tokens_neox': 2058, 'n_words': 348}