emmylua_ls 0.25.1

A language server for emmylua.
Documentation
use std::collections::HashMap;

use emmylua_code_analysis::{DbIndex, LuaSemanticDeclId, LuaType, LuaTypeDeclId, TypeSubstitutor};

use crate::handlers::hover::{HoverBuilder, HoverDeclContext};

use super::{
    define_hover::{HoverFunctionInfo, set_function_info_to_builder},
    extract_function_member,
    generic::{instantiate_type_if_needed, owner_type_substitutor, unknown_type_substitutor},
    get_function_description,
    render::process_function_type,
};

type OwnerSubstitutorCache = HashMap<LuaTypeDeclId, Option<TypeSubstitutor>>;

pub(super) fn build_table_field_hover(
    builder: &mut HoverBuilder,
    db: &DbIndex,
    decl_context: &HoverDeclContext,
    parent_table_type: &LuaType,
) -> Option<()> {
    let mut function_infos = Vec::new();
    let mut substitutor_cache = OwnerSubstitutorCache::new();
    for decl_info in decl_context.ordered_decl_refs() {
        let semantic_decl_id = decl_info.id();
        let typ = resolve_semantic_decl_type(
            db,
            semantic_decl_id,
            decl_info.typ(),
            parent_table_type,
            &mut substitutor_cache,
        );
        let function_member = extract_function_member(db, semantic_decl_id);

        let Some(contents) =
            process_function_type(builder, db, &typ, semantic_decl_id, function_member, None)
        else {
            continue;
        };
        if contents.is_empty() {
            continue;
        }

        let description = get_function_description(builder, db, semantic_decl_id);
        if let Some(info) = HoverFunctionInfo::from_contents(contents, description) {
            function_infos.push(info);
        }
    }

    set_function_info_to_builder(builder, &mut function_infos)
}

fn resolve_semantic_decl_type(
    db: &DbIndex,
    semantic_decl: &LuaSemanticDeclId,
    typ: &LuaType,
    parent_table_type: &LuaType,
    substitutor_cache: &mut OwnerSubstitutorCache,
) -> LuaType {
    if !typ.contain_tpl() {
        return typ.clone();
    }

    let Some(owner_type_id) = semantic_decl_owner_type_id(db, semantic_decl) else {
        return typ.clone();
    };
    let substitutor =
        cached_substitutor_for_owner(db, parent_table_type, owner_type_id, substitutor_cache);

    substitutor
        .and_then(|substitutor| instantiate_type_if_needed(db, typ, &substitutor))
        .unwrap_or_else(|| typ.clone())
}

fn cached_substitutor_for_owner(
    db: &DbIndex,
    parent_table_type: &LuaType,
    owner_type_id: LuaTypeDeclId,
    substitutor_cache: &mut OwnerSubstitutorCache,
) -> Option<TypeSubstitutor> {
    if let Some(substitutor) = substitutor_cache.get(&owner_type_id) {
        return substitutor.clone();
    }

    let substitutor = owner_type_substitutor(db, parent_table_type, &owner_type_id)
        .or_else(|| unknown_type_substitutor(db, &owner_type_id));
    substitutor_cache.insert(owner_type_id, substitutor.clone());
    substitutor
}

fn semantic_decl_owner_type_id(
    db: &DbIndex,
    semantic_decl: &LuaSemanticDeclId,
) -> Option<LuaTypeDeclId> {
    match semantic_decl {
        LuaSemanticDeclId::Member(id) => db
            .get_member_index()
            .get_current_owner(id)?
            .get_type_id()
            .cloned(),
        _ => None,
    }
}