Skip to content
Merged

update #1150

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
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ repos:
- id: check-symlinks
- id: destroyed-symlinks
- id: check-vcs-permalinks
- repo: https://github.com/crate-ci/typos
rev: master
hooks:
- id: typos
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,9 @@ pub fn analyze_param(analyzer: &mut DocAnalyzer, tag: LuaDocTagParam) -> Option<
};

let nullable = tag.is_nullable();
let mut type_ref = if let Some(lua_doc_type) = tag.get_type() {
let mut type_ref = {
let lua_doc_type = tag.get_type()?;
infer_type(&mut analyzer.type_context, lua_doc_type)
} else {
return None;
};

if nullable && !type_ref.is_nullable() {
Expand Down
125 changes: 125 additions & 0 deletions crates/emmylua_code_analysis/src/compilation/test/generic_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,131 @@ mod test {
assert_eq!(ws.humanize_type(result_ty), "integer");
}

#[test]
fn test_call_operator_self_infer_on_index() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@class Factory
---@overload fun(): self

---@class Mod
---@field Factory Factory
---@type Mod
local Mod

result = Mod.Factory()
"#,
);

let result_ty = ws.expr_ty("result");
assert_eq!(ws.humanize_type(result_ty), "Factory");
}

#[test]
fn test_call_operator_self_infer_filters_union_receiver() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@class Callable
---@overload fun(): self

---@type Callable|string
local value

result = value()
"#,
);

let result_ty = ws.expr_ty("result");
assert_eq!(ws.humanize_type(result_ty), "Callable");
}

#[test]
fn test_call_operator_self_infer_through_generic_alias() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@class Callable
---@overload fun(): self

---@alias Box<T> T

---@type Box<Callable>
local value

result = value()
"#,
);

let result_ty = ws.expr_ty("result");
assert_eq!(ws.humanize_type(result_ty), "Callable");
}

#[test]
fn test_call_operator_self_infer_through_intersection() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@class Callable
---@overload fun(): self

---@class Extra

---@type Callable & Extra
local value

result = value()
"#,
);

let result_ty = ws.expr_ty("result");
assert_eq!(ws.humanize_type(result_ty), "(Callable & Extra)");
}

#[test]
fn test_colon_call_generic_uses_receiver_when_member_is_callable() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@class Owner
---@field run CallableMethod

---@class CallableMethod
---@overload fun<T>(owner: T): T

---@type Owner
local owner

result = owner:run()
"#,
);

let result_ty = ws.expr_ty("result");
assert_eq!(ws.humanize_type(result_ty), "Owner");
}

#[test]
fn test_plain_table_metatable_call_return_self() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();
ws.def(
r#"
factory = setmetatable({}, {
---@return self
__call = function(self)
return self
end,
})

result = factory()
"#,
);

let result_ty = ws.expr_ty("result");
let humanized = ws.humanize_type(result_ty);
assert_eq!(humanized, "table");
}

#[test]
fn test_function_generic_constraint_is_fallback() {
let mut ws = VirtualWorkspace::new();
Expand Down
4 changes: 2 additions & 2 deletions crates/emmylua_code_analysis/src/config/config_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub fn load_configs_raw(config_files: Vec<PathBuf>, partial_emmyrcs: Option<Vec<
Err(e) => {
log::error!(
"Failed to parse lua config file: {:?}, error: {:?}",
&config_file,
config_file,
e
);
continue;
Expand All @@ -40,7 +40,7 @@ pub fn load_configs_raw(config_files: Vec<PathBuf>, partial_emmyrcs: Option<Vec<
Err(e) => {
log::error!(
"Failed to parse config file: {:?}, error: {:?}",
&config_file,
config_file,
e
);
continue;
Expand Down
12 changes: 6 additions & 6 deletions crates/emmylua_code_analysis/src/db_index/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ impl LuaModuleIndex {
let node = self.module_nodes.get_mut(&parent_node_id)?;

node.file_ids.push(file_id);
let module_name = match module_parts.last() {
Some(name) => name.to_string(),
None => return None,
let module_name = {
let name = module_parts.last()?;
name.to_string()
};
let module_info = ModuleInfo {
file_id,
Expand Down Expand Up @@ -274,9 +274,9 @@ impl LuaModuleIndex {
let mut parent_node_id = self.module_root_id;
for part in module_parts {
let parent_node = self.module_nodes.get(&parent_node_id)?;
let child_id = match parent_node.children.get(*part) {
Some(id) => *id,
None => return None,
let child_id = {
let id = parent_node.children.get(*part)?;
*id
};
parent_node_id = child_id;
}
Expand Down
38 changes: 34 additions & 4 deletions crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use itertools::Itertools;

use crate::{
AsyncState, DbIndex, LuaAliasCallType, LuaConditionalType, LuaFunctionType, LuaGenericType,
LuaIntersectionType, LuaMemberKey, LuaMemberOwner, LuaObjectType, LuaSignatureId,
LuaStringTplType, LuaTupleType, LuaType, LuaTypeDeclId, LuaUnionType, TypeSubstitutor,
VariadicType,
LuaIntersectionType, LuaMappedType, LuaMemberKey, LuaMemberOwner, LuaObjectType,
LuaSignatureId, LuaStringTplType, LuaTupleType, LuaType, LuaTypeDeclId, LuaUnionType,
TypeSubstitutor, VariadicType,
};

use super::{LuaAliasCallKind, LuaMultiLineUnion};
Expand Down Expand Up @@ -217,9 +217,9 @@ impl<'a> TypeHumanizer<'a> {
}
LuaType::Language(s) => w.write_str(s),
LuaType::Conditional(c) => self.write_conditional_type(c, w),
LuaType::Mapped(mapped) => self.write_mapped_type(mapped, w),
LuaType::Never => w.write_str("never"),
LuaType::ModuleRef(file_id) => self.write_module_ref(*file_id, w),
_ => w.write_str("unknown"),
}
}

Expand Down Expand Up @@ -1082,6 +1082,36 @@ impl<'a> TypeHumanizer<'a> {
Ok(())
}

// ─── Mapped ─────────────────────────────────────────────────────

fn write_mapped_type<W: Write>(&mut self, mapped: &LuaMappedType, w: &mut W) -> fmt::Result {
w.write_str("{ ")?;
if mapped.is_readonly {
w.write_str("readonly ")?;
}

w.write_char('[')?;
w.write_str(mapped.param.1.name.as_str())?;
w.write_str(" in ")?;

let saved = self.level;
self.level = self.child_level();
if let Some(constraint) = &mapped.param.1.constraint {
self.write_type(constraint, w)?;
} else {
w.write_str("unknown")?;
}
w.write_char(']')?;
if mapped.is_optional {
w.write_char('?')?;
}
w.write_str(": ")?;
self.write_type(&mapped.value, w)?;
self.level = saved;

w.write_str("; }")
}

// ─── ModuleRef ──────────────────────────────────────────────────

fn write_module_ref<W: Write>(&mut self, file_id: crate::FileId, w: &mut W) -> fmt::Result {
Expand Down
4 changes: 2 additions & 2 deletions crates/emmylua_code_analysis/src/db_index/type/type_decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,11 @@ impl Serialize for LuaTypeDeclId {
match self.id.as_ref() {
LuaTypeIdentifier::Global(name) => serializer.serialize_str(name.as_ref()),
LuaTypeIdentifier::Internal(workspace_id, name) => {
let s = format!("ws:{}|{}", workspace_id.id, &name);
let s = format!("ws:{}|{}", workspace_id.id, name);
serializer.serialize_str(&s)
}
LuaTypeIdentifier::File(file_id, name) => {
let s = format!("{}|{}", file_id.id, &name);
let s = format!("{}|{}", file_id.id, name);
serializer.serialize_str(&s)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub(super) fn build_call_constraint_context(
params.insert(0, ("self".into(), Some(LuaType::SelfInfer)));
}
(true, false) => {
let self_type = semantic_model.infer_call_self_type(call_expr)?;
let self_type = semantic_model.resolve_call_self_type(call_expr)?;
args.insert(
0,
CallConstraintArg {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use hashbrown::{HashMap, HashSet};

use emmylua_parser::{LuaAst, LuaAstNode, LuaSyntaxId, LuaTableExpr};
use emmylua_parser::{LuaAst, LuaAstNode, LuaExpr, LuaSyntaxId, LuaTableExpr};
use rowan::NodeOrToken;

use crate::{
Expand Down Expand Up @@ -63,6 +63,13 @@ fn check_table_expr(
let table_type = match semantic_model.infer_table_should_be(expr.clone())? {
LuaType::Union(union) => {
let mut check_type = None;
let array_like_expr_type = if expr.is_array() || expr.is_empty() {
semantic_model
.infer_expr(LuaExpr::TableExpr(expr.clone()))
.ok()
} else {
None
};
for ty in union.into_vec() {
match &ty {
LuaType::Ref(_)
Expand All @@ -74,10 +81,14 @@ fn check_table_expr(
}
check_type = Some(ty);
}
LuaType::Table | LuaType::Userdata => {
LuaType::Table | LuaType::Userdata | LuaType::TableGeneric(_) => {
return Some(());
}
LuaType::TableGeneric(_) => {
LuaType::Array(_) | LuaType::Tuple(_)
if array_like_expr_type.as_ref().is_some_and(|expr_type| {
semantic_model.type_check(&ty, expr_type).is_ok()
}) =>
{
return Some(());
}
_ => {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub(super) fn check_param_types(
.into_iter()
.unzip();

let self_type = semantic_model.infer_call_self_type(&facts.call_expr);
let self_type = semantic_model.resolve_call_self_type(&facts.call_expr);
let colon_range = facts
.call_expr
.get_colon_token()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,40 @@ return t
));
}

#[test]
fn test_function_parameter_contravariance_assignment() {
let mut ws = VirtualWorkspace::new();
ws.def(
r#"
---@class A
---@class B
---@class C
---@class D

---@param a A | B | C
---@return boolean
function condition(a)
return true
end
"#,
);
assert!(!ws.has_no_diagnostic(
DiagnosticCode::AssignTypeMismatch,
r#"
---@type fun(a: A | B | C | D): boolean
local tmp = condition
"#,
));

assert!(ws.has_no_diagnostic(
DiagnosticCode::AssignTypeMismatch,
r#"
---@type fun(a: A | B): boolean
local tmp = condition
"#,
));
}

#[test]
fn test_generic_extends_table() {
let mut ws = VirtualWorkspace::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,26 @@ mod test {
));
}

#[test]
fn test_colon_call_constraint_uses_receiver_when_member_is_callable() {
let mut ws = VirtualWorkspace::new();
assert!(ws.has_no_diagnostic(
DiagnosticCode::GenericConstraintMismatch,
r#"
---@class Owner
---@field run CallableMethod

---@class CallableMethod
---@overload fun<T: Owner>(owner: T)

---@type Owner
local owner

owner:run()
"#,
));
}

#[test]
fn test_extend_string() {
let mut ws = VirtualWorkspace::new();
Expand Down
Loading
Loading