Skip to content
Draft
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
7 changes: 7 additions & 0 deletions crates/emmylua_code_analysis/resources/std/builtin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@
--- Extract from T those types that are assignable to U
--- @alias Extract<T, U> T extends U and T or never

---
--- From T, pick a set of properties whose keys are in the union K
--- @alias Pick<T, K extends keyof T> {[P in K]: T[P]; }

---
--- Construct a type with the properties of T except for those in type K.
--- @alias Omit<T, K extends keyof any> Pick<T, Exclude<keyof T, K>>

--- attribute

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,24 @@ use crate::{GenericParam, GenericTpl, GenericTplId, LuaType};
pub trait GenericIndex: std::fmt::Debug {
fn add_generic_scope(&mut self, ranges: Vec<TextRange>, is_func: bool) -> GenericScopeId;

fn append_generic_param(&mut self, scope_id: GenericScopeId, param: GenericParam);

fn append_generic_params(&mut self, scope_id: GenericScopeId, params: Vec<GenericParam>) {
fn append_generic_param(
&mut self,
scope_id: GenericScopeId,
param: GenericParam,
) -> Option<GenericTplId>;

fn append_generic_params(
&mut self,
scope_id: GenericScopeId,
params: Vec<GenericParam>,
) -> Vec<GenericParam> {
let mut appended = Vec::new();
for param in params {
self.append_generic_param(scope_id, param);
if let Some(tpl_id) = self.append_generic_param(scope_id, param.clone()) {
appended.push(param.with_tpl_id(Some(tpl_id)));
}
}
appended
}

fn find_generic(
Expand Down Expand Up @@ -63,16 +75,15 @@ impl GenericIndex for FileGenericIndex {
scope_id
}

fn append_generic_param(&mut self, scope_id: GenericScopeId, param: GenericParam) {
fn append_generic_param(
&mut self,
scope_id: GenericScopeId,
param: GenericParam,
) -> Option<GenericTplId> {
if let Some(scope) = self.scopes.get_mut(scope_id.id) {
scope.insert_param(param);
}
}

fn append_generic_params(&mut self, scope_id: GenericScopeId, params: Vec<GenericParam>) {
for param in params {
self.append_generic_param(scope_id, param);
return Some(scope.insert_param(param));
}
None
}

/// Find generic parameter by position and name.
Expand Down Expand Up @@ -131,10 +142,12 @@ impl FileGenericScope {
self.next_tpl_id.is_func()
}

fn insert_param(&mut self, param: GenericParam) {
let tpl_id = self.next_tpl_id;
self.next_tpl_id = self.next_tpl_id.with_idx((tpl_id.get_idx() + 1) as u32);
fn insert_param(&mut self, param: GenericParam) -> GenericTplId {
let tpl_id = param.tpl_id.unwrap_or(self.next_tpl_id);
let next_idx = self.next_tpl_id.get_idx().max(tpl_id.get_idx() + 1) as u32;
self.next_tpl_id = self.next_tpl_id.with_idx(next_idx);
self.params.insert(param.name.to_string(), (tpl_id, param));
tpl_id
}

fn contains(&self, position: TextSize) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ pub fn analyze_alias(analyzer: &mut DocAnalyzer, tag: LuaDocTagAlias) -> Option<
alias_decl.get_id()
};

let type_node = tag.get_type()?;
if tag.get_generic_decl_list().is_some() {
let generic_params = get_type_generic_params(analyzer, &alias_decl_id);
let range = analyzer.comment.get_range();
let range = type_node.get_range();
let scope_id = analyzer
.type_context
.generic_index
Expand All @@ -155,7 +156,7 @@ pub fn analyze_alias(analyzer: &mut DocAnalyzer, tag: LuaDocTagAlias) -> Option<
.append_generic_params(scope_id, generic_params);
}

let mut origin_type = infer_type(&mut analyzer.type_context, tag.get_type()?);
let mut origin_type = infer_type(&mut analyzer.type_context, type_node);
if alias_origin_reaches(analyzer.get_db(), &origin_type, &alias_decl_id) {
origin_type = LuaType::Any;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ fn normalize_generic_params(db: &DbIndex, params: &[GenericParam]) -> Vec<Generi
.map(|ty| complete_type_generic_args_in_type(db, ty)),
param.attributes.clone(),
)
.with_tpl_id(param.tpl_id)
})
.collect()
}
Expand Down Expand Up @@ -97,8 +98,9 @@ fn resolve_generic_params(
});

let param = GenericParam::new(name, constraint, default_type, None);
generic_index.append_generic_param(scope_id, param.clone());
params.push(param);
if let Some(tpl_id) = generic_index.append_generic_param(scope_id, param.clone()) {
params.push(param.with_tpl_id(Some(tpl_id)));
}
}

params
Expand Down Expand Up @@ -156,13 +158,16 @@ impl GenericIndex for HeaderGenericIndex {
id
}

fn append_generic_param(&mut self, scope_id: GenericScopeId, param: GenericParam) {
let Some(scope) = self.scopes.get_mut(scope_id.id) else {
return;
};
fn append_generic_param(
&mut self,
scope_id: GenericScopeId,
param: GenericParam,
) -> Option<GenericTplId> {
let scope = self.scopes.get_mut(scope_id.id)?;
let tpl_id = scope.next_tpl_id;
scope.next_tpl_id = scope.next_tpl_id.with_idx((tpl_id.get_idx() + 1) as u32);
scope.params.push((tpl_id, param));
Some(tpl_id)
}

fn find_generic(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use emmylua_parser::{LuaAstToken, LuaExpr, LuaForRangeStat};
use crate::{
DbIndex, InferFailReason, LuaDeclId, LuaInferCache, LuaOperatorMetaMethod, LuaType,
LuaTypeCache, TplContext, TypeOps, TypeSubstitutor, VariadicType,
compilation::analyzer::unresolve::UnResolveIterVar, infer_expr, instantiate_doc_function,
compilation::analyzer::unresolve::UnResolveIterVar, infer_expr, instantiate_type_generic,
tpl_pattern_match_args,
};

Expand Down Expand Up @@ -145,22 +145,18 @@ pub fn infer_for_range_iter_expr_func(
return Ok(doc_function.get_variadic_ret());
};
let mut substitutor = TypeSubstitutor::new();
let mut context = TplContext {
db,
cache,
substitutor: &mut substitutor,
call_expr: None,
};
let params = doc_function
.get_params()
.iter()
.map(|(_, opt_ty)| opt_ty.clone().unwrap_or(LuaType::Any))
.collect::<Vec<_>>();

let mut context = TplContext::new(db, cache, &mut substitutor, None);
tpl_pattern_match_args(&mut context, &params, &[status_param])?;

let doc_function_ty = LuaType::DocFunction(doc_function.clone());
let instantiate_func = if let LuaType::DocFunction(f) =
instantiate_doc_function(db, &doc_function, &substitutor)
instantiate_type_generic(db, &doc_function_ty, &substitutor)
{
f
} else {
Expand Down
150 changes: 137 additions & 13 deletions crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ pub fn analyze_local_stat(analyzer: &mut LuaAnalyzer, local_stat: LuaLocalStat)
break;
};

pre_analyze_call_arg_table_fields(analyzer, &expr);

match analyzer.infer_expr(&expr) {
Ok(expr_type) => {
let expr_type = expr_type.get_result_slot_type(0).unwrap_or(expr_type);
let decl_id = LuaDeclId::new(analyzer.file_id, position);
// 当`call`参数包含表时, 表可能未被分析, 需要延迟
// 当表达式中存在带表参数的调用时, 表可能尚未完成预分析, 需要延迟
if let LuaType::Instance(instance) = &expr_type
&& instance.get_base().is_unknown()
&& call_expr_has_effect_table_arg(&expr).is_some()
Expand Down Expand Up @@ -162,17 +164,7 @@ pub fn analyze_local_stat(analyzer: &mut LuaAnalyzer, local_stat: LuaLocalStat)
}

fn call_expr_has_effect_table_arg(expr: &LuaExpr) -> Option<()> {
if let LuaExpr::CallExpr(call_expr) = expr {
let args_list = call_expr.get_args_list()?;
for arg in args_list.get_args() {
if let LuaExpr::TableExpr(table_expr) = arg
&& !table_expr.is_empty()
{
return Some(());
}
}
}
None
expr_has_effect_table_call_arg(expr.clone())
}

fn get_var_owner(analyzer: &mut LuaAnalyzer, var: LuaVarExpr) -> LuaTypeOwner {
Expand Down Expand Up @@ -309,6 +301,8 @@ pub fn analyze_assign_stat(analyzer: &mut LuaAnalyzer, assign_stat: LuaAssignSta
continue;
}

pre_analyze_call_arg_table_fields(analyzer, expr);

let expr_type = match analyzer.infer_expr(expr) {
Ok(expr_type) => expr_type.get_result_slot_type(0).unwrap_or(expr_type),
Err(InferFailReason::None) => LuaType::Unknown,
Expand Down Expand Up @@ -529,8 +523,17 @@ pub fn analyze_table_field(analyzer: &mut LuaAnalyzer, field: LuaTableField) ->
}
}

let value_expr = field.get_value_expr()?;
let member_id = LuaMemberId::new(field.get_syntax_id(), analyzer.file_id);
if analyzer
.db
.get_type_index()
.get_type_cache(&member_id.into())
.is_some()
{
return Some(());
}
let value_expr = field.get_value_expr()?;

let value_type = match analyzer.infer_expr(&value_expr.clone()) {
Ok(value_type) => match value_type {
LuaType::Def(ref_id) => LuaType::Ref(ref_id),
Expand Down Expand Up @@ -620,3 +623,124 @@ fn get_delayed_definition_decl_id(
}
Some(decl_id)
}

fn pre_analyze_call_arg_table_fields(analyzer: &mut LuaAnalyzer, expr: &LuaExpr) {
pre_analyze_nested_table_fields(analyzer, expr.clone());
}

fn pre_analyze_nested_table_fields(analyzer: &mut LuaAnalyzer, expr: LuaExpr) {
match expr {
LuaExpr::CallExpr(call_expr) => {
if let Some(prefix_expr) = call_expr.get_prefix_expr() {
pre_analyze_nested_table_fields(analyzer, prefix_expr);
}

if let Some(args_list) = call_expr.get_args_list() {
for arg in args_list.get_args() {
pre_analyze_nested_table_fields(analyzer, arg);
}
}
}
LuaExpr::TableExpr(table_expr) => {
for field in table_expr.get_fields() {
if let Some(LuaIndexKey::Expr(key_expr)) = field.get_field_key() {
pre_analyze_nested_table_fields(analyzer, key_expr);
}

if let Some(value_expr) = field.get_value_expr() {
pre_analyze_nested_table_fields(analyzer, value_expr);
}

analyze_table_field(analyzer, field.clone());
}
}
LuaExpr::BinaryExpr(binary_expr) => {
if let Some((left, right)) = binary_expr.get_exprs() {
pre_analyze_nested_table_fields(analyzer, left);
pre_analyze_nested_table_fields(analyzer, right);
}
}
LuaExpr::UnaryExpr(unary_expr) => {
if let Some(inner_expr) = unary_expr.get_expr() {
pre_analyze_nested_table_fields(analyzer, inner_expr);
}
}
LuaExpr::ParenExpr(paren_expr) => {
if let Some(inner_expr) = paren_expr.get_expr() {
pre_analyze_nested_table_fields(analyzer, inner_expr);
}
}
LuaExpr::IndexExpr(index_expr) => {
if let Some(prefix_expr) = index_expr.get_prefix_expr() {
pre_analyze_nested_table_fields(analyzer, prefix_expr);
}

if let Some(LuaIndexKey::Expr(key_expr)) = index_expr.get_index_key() {
pre_analyze_nested_table_fields(analyzer, key_expr);
}
}
LuaExpr::LiteralExpr(_) | LuaExpr::ClosureExpr(_) | LuaExpr::NameExpr(_) => {}
}
}
Comment thread
xuhuanzy marked this conversation as resolved.

fn expr_has_effect_table_call_arg(expr: LuaExpr) -> Option<()> {
match expr {
LuaExpr::CallExpr(call_expr) => {
if let Some(prefix_expr) = call_expr.get_prefix_expr()
&& expr_has_effect_table_call_arg(prefix_expr).is_some()
{
return Some(());
}

let args_list = call_expr.get_args_list()?;
for arg in args_list.get_args() {
if let LuaExpr::TableExpr(table_expr) = &arg
&& !table_expr.is_empty()
{
return Some(());
}

if expr_has_effect_table_call_arg(arg).is_some() {
return Some(());
}
}
None
}
LuaExpr::TableExpr(table_expr) => {
for field in table_expr.get_fields() {
if let Some(LuaIndexKey::Expr(key_expr)) = field.get_field_key()
&& expr_has_effect_table_call_arg(key_expr).is_some()
{
return Some(());
}

if let Some(value_expr) = field.get_value_expr()
&& expr_has_effect_table_call_arg(value_expr).is_some()
{
return Some(());
}
}
None
}
LuaExpr::BinaryExpr(binary_expr) => {
let (left, right) = binary_expr.get_exprs()?;
expr_has_effect_table_call_arg(left).or_else(|| expr_has_effect_table_call_arg(right))
}
LuaExpr::UnaryExpr(unary_expr) => expr_has_effect_table_call_arg(unary_expr.get_expr()?),
LuaExpr::ParenExpr(paren_expr) => expr_has_effect_table_call_arg(paren_expr.get_expr()?),
LuaExpr::IndexExpr(index_expr) => {
if let Some(prefix_expr) = index_expr.get_prefix_expr()
&& expr_has_effect_table_call_arg(prefix_expr).is_some()
{
return Some(());
}

if let Some(LuaIndexKey::Expr(key_expr)) = index_expr.get_index_key() {
return expr_has_effect_table_call_arg(key_expr);
}

None
}
LuaExpr::LiteralExpr(_) | LuaExpr::ClosureExpr(_) | LuaExpr::NameExpr(_) => None,
}
}
Loading
Loading