Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions crates/emmylua_code_analysis/src/semantic_model/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,15 +607,39 @@ impl<'db> SemanticModel<'db> {
/// cross-file globals) with the full signature projection (main + `---@overload`)
/// so all rendering paths share one candidate list.
pub fn callable_candidates_for_expr(&self, callee: &LuaExpr) -> Vec<LuaFunctionType> {
let mut out = callable_candidates_uncached(self, callee);
// The diagnostic path may only project the callee value type; append the
// full signature candidates (main + `---@overload`) for name callees.
let mut out = self.callable_candidates_cached(callee);
// The value type may omit `---@overload` signatures. Keep its richer
// generic projection when there are no overloads to add.
if let LuaExpr::NameExpr(name_expr) = callee
&& let Some(owner) = self.resolve_name(name_expr.get_position())
&& let SemanticId::Decl(key) = &owner
&& let Some(facts) = self.file_facts_of(key.file_id)
&& let Some(decl) = facts.decl_by_id(&owner)
&& let Some(original) = decl.value_expr_syntax
&& let Some(signature) = facts.signature_by_closure(original)
&& signature
.docs
.as_ref()
.is_some_and(|docs| !docs.overloads.is_empty())
{
let vm = infer::vm::InferVm::new(self, &[]);
if let Some(candidates) = vm.callable_candidates_for_owner(&owner) {
for candidate in candidates {
if let Some(projection) = vm.signature_projection(key.file_id, signature) {
let mut candidates = projection.overloads;
// Keep the value's richer projection if the original main is present.
// A replacement does not cover a main on another live path.
if !self
.type_of_signature_in_file(key.file_id, original)
.is_some_and(|main| out.contains(&main))
{
candidates.extend(projection.main);
}
for candidate in flow::original_decl_signatures_at(
self,
&owner,
original,
name_expr.get_position(),
candidates,
) {
if !out.contains(&candidate) {
out.push(candidate);
}
Expand Down
33 changes: 30 additions & 3 deletions crates/emmylua_code_analysis/src/semantic_model/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,37 @@ impl<T> CacheEntry<T> {
}
}

/// Immutable signature projection supplied by the caller. Equality includes the
/// generic bindings and whether the caller included the main signature.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct OriginalSignatures {
pub(super) closure: LuaSyntaxId,
pub(super) signatures: Rc<Vec<LuaFunctionType>>,
}

/// The three declaration queries supported by the shared flow walk.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum DeclTracePurpose {
ReadValue,
/// Use the declaration type and casts, without assignments or branch guards.
AssignTarget,
OriginalSignatures(OriginalSignatures),
}

/// Syntax facts used to choose a safe return-type query for a local function.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum LocalCallableFlow {
Reassigned,
Unchanged,
/// The value is unchanged, but a named cast can change its exposed type.
Cast,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum TraceSubject {
Decl {
decl: SemanticId,
guards: bool,
casts: bool,
assignments: bool,
purpose: DeclTracePurpose,
merge_branch: bool,
},
Member {
Expand All @@ -185,6 +209,8 @@ pub(crate) enum TraceSubject {
}

/// Memo key for one flow-trace state on a loop-free graph: `(file, subject, flow node)`.
/// Original-signature traces keep incoming PathState empty and memoize each
/// node's transfer during unwind, so a cached node never contains later casts.
///
/// The incoming path is deliberately absent. It is safe to omit only for flow
/// graphs without loops, where every route to a flow node crosses the same
Expand Down Expand Up @@ -229,6 +255,7 @@ pub(crate) struct SemanticLocalCache {
pub(crate) builtin_function: HashMap<(FileId, BuiltinKey), Option<SmolStr>>,
/// Whether all writes to a local occur in its own closure.
pub(crate) builtin_local_writes: HashMap<(FileId, SemanticId), bool>,
pub(crate) callable_local_flow: HashMap<(FileId, SemanticId), LocalCallableFlow>,
pub(crate) resolve_owner_set: HashMap<SemanticId, Vec<SemanticId>>,
pub(crate) member_type_at: HashMap<(FileId, SemanticId, TextSize), LuaType>,
pub(crate) flow_decl: HashMap<(FileId, SemanticId, FlowId), LuaType>,
Expand Down
Loading
Loading