Merge pull request #389 from erg-lang/feat-unused-warn

Add warnings for unused vars
This commit is contained in:
Shunsuke Shibayama 2023-02-07 01:23:04 +09:00 committed by GitHub
commit ce2a17a2cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 545 additions and 228 deletions

118
crates/els/code_action.rs Normal file
View file

@ -0,0 +1,118 @@
use std::collections::HashMap;
use serde::Deserialize;
use serde_json::json;
use serde_json::Value;
use erg_common::traits::Locational;
use erg_compiler::artifact::BuildRunnable;
use erg_compiler::hir::Expr;
use lsp_types::{
CodeAction, CodeActionKind, CodeActionParams, Diagnostic, TextEdit, Url, WorkspaceEdit,
};
use crate::server::{ELSResult, Server};
use crate::util;
impl<Checker: BuildRunnable> Server<Checker> {
fn send_eliminate_unused_vars_action(
&self,
_msg: &Value,
uri: &Url,
diag: Diagnostic,
) -> ELSResult<Vec<CodeAction>> {
let mut map = HashMap::new();
let Some(visitor) = self.get_visitor(uri) else {
Self::send_log("visitor not found")?;
return Ok(vec![]);
};
let Some(expr) = visitor.get_min_expr(diag.range) else {
Self::send_log("expr not found")?;
return Ok(vec![]);
};
if let Expr::Def(def) = expr {
let mut range = util::loc_to_range(def.loc()).unwrap();
let next = lsp_types::Range {
start: lsp_types::Position {
line: range.end.line,
character: range.end.character,
},
end: lsp_types::Position {
line: range.end.line,
character: range.end.character + 1,
},
};
let code = util::get_ranged_code_from_uri(uri, next)?;
match code.as_ref().map(|s| &s[..]) {
None => {
// \n
range.end.line += 1;
range.end.character = 0;
}
Some(";") => range.end.character += 1,
Some(other) => {
Self::send_log(format!("? {other}"))?;
}
}
let edit = TextEdit::new(range, "".to_string());
map.insert(uri.clone(), vec![edit]);
let edit = WorkspaceEdit::new(map);
let action = CodeAction {
title: "Eliminate unused variables".to_string(),
kind: Some(CodeActionKind::QUICKFIX),
diagnostics: Some(vec![diag]),
edit: Some(edit),
..Default::default()
};
Ok(vec![action])
} else {
Ok(vec![])
}
}
pub(crate) fn send_code_action(&self, msg: &Value) -> ELSResult<()> {
Self::send_log(format!("code action requested: {msg}"))?;
let params = CodeActionParams::deserialize(&msg["params"])?;
let result = match params
.context
.only
.as_ref()
.and_then(|kinds| kinds.first().map(|s| s.as_str()))
{
Some("quickfix") => self.send_quick_fix(msg, params)?,
None => self.send_normal_action(msg, params)?,
Some(other) => {
Self::send_log(&format!("Unknown code action requested: {other}"))?;
vec![]
}
};
Self::send(
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": result }),
)
}
fn send_normal_action(
&self,
msg: &Value,
params: CodeActionParams,
) -> ELSResult<Vec<CodeAction>> {
self.send_quick_fix(msg, params)
}
fn send_quick_fix(&self, msg: &Value, params: CodeActionParams) -> ELSResult<Vec<CodeAction>> {
let mut result: Vec<CodeAction> = vec![];
let uri = util::normalize_url(params.text_document.uri);
for diag in params.context.diagnostics.into_iter() {
match &diag.message {
unused if unused.ends_with("is not used") => {
result.extend(self.send_eliminate_unused_vars_action(msg, &uri, diag)?);
}
_ => {
Self::send_log(&format!("Unknown diagnostic for action: {}", diag.message))?;
}
}
}
Ok(result)
}
}

View file

@ -4,7 +4,7 @@ use erg_compiler::erg_parser::token::Token;
use erg_compiler::hir::*;
use erg_compiler::ty::{HasType, Type};
use erg_compiler::varinfo::VarInfo;
use lsp_types::{Position, Url};
use lsp_types::{Position, Range, Url};
use crate::util;
@ -41,6 +41,72 @@ impl<'a> HIRVisitor<'a> {
matches!(chunk, Expr::Call(_) | Expr::Lambda(_) | Expr::Def(_) | Expr::ClassDef(_) if cond)
}
pub fn get_min_expr(&self, range: Range) -> Option<&Expr> {
self.get_min_expr_from_exprs(self.hir.module.iter(), range)
}
// TODO:
fn get_min_expr_from_expr<'e>(&self, expr: &'e Expr, range: Range) -> Option<&'e Expr> {
match expr {
Expr::Def(def) => {
if let Some(expr) = self.get_min_expr_from_exprs(def.body.block.iter(), range) {
Some(expr)
} else if util::pos_in_loc(def, range.start) {
Some(expr)
} else {
None
}
}
Expr::Lambda(lambda) => {
if let Some(expr) = self.get_min_expr_from_exprs(lambda.body.iter(), range) {
Some(expr)
} else if util::pos_in_loc(lambda, range.start) {
Some(expr)
} else {
None
}
}
Expr::ClassDef(cdef) => {
if let Some(expr) = self.get_min_expr_from_exprs(cdef.methods.iter(), range) {
Some(expr)
} else if util::pos_in_loc(cdef, range.start) {
Some(expr)
} else {
None
}
}
Expr::PatchDef(pdef) => {
if let Some(expr) = self.get_min_expr_from_exprs(pdef.methods.iter(), range) {
Some(expr)
} else if util::pos_in_loc(pdef, range.start) {
Some(expr)
} else {
None
}
}
other => {
if util::pos_in_loc(other, range.start) {
Some(expr)
} else {
None
}
}
}
}
fn get_min_expr_from_exprs<'e>(
&self,
iter: impl Iterator<Item = &'e Expr>,
range: Range,
) -> Option<&'e Expr> {
for chunk in iter {
if let Some(expr) = self.get_min_expr_from_expr(chunk, range) {
return Some(expr);
}
}
None
}
fn get_expr_ns(&self, cur_ns: Vec<Str>, chunk: &Expr, pos: Position) -> Option<Vec<Str>> {
if !(util::pos_in_loc(chunk, pos) || self.is_new_final_line(chunk, pos)) {
return None;

View file

@ -1,3 +1,4 @@
mod code_action;
mod completion;
mod definition;
mod diagnostics;

View file

@ -1,3 +1,4 @@
mod code_action;
mod completion;
mod definition;
mod diagnostics;

View file

@ -19,9 +19,9 @@ impl<Checker: BuildRunnable> Server<Checker> {
if let Some(visitor) = self.get_visitor(&uri) {
if let Some(vi) = visitor.get_info(&tok) {
let mut refs = vec![];
if let Some(referrers) = self.get_index().get_refs(&vi.def_loc) {
if let Some(value) = self.get_index().get_refs(&vi.def_loc) {
// Self::send_log(format!("referrers: {referrers:?}"))?;
for referrer in referrers.iter() {
for referrer in value.referrers.iter() {
if let (Some(path), Some(range)) =
(&referrer.module, util::loc_to_range(referrer.loc))
{

View file

@ -45,9 +45,9 @@ impl<Checker: BuildRunnable> Server<Checker> {
}
let mut changes: HashMap<Url, Vec<TextEdit>> = HashMap::new();
Self::commit_change(&mut changes, &vi.def_loc, params.new_name.clone());
if let Some(referrers) = self.get_index().get_refs(&vi.def_loc) {
if let Some(value) = self.get_index().get_refs(&vi.def_loc) {
// Self::send_log(format!("referrers: {referrers:?}"))?;
for referrer in referrers.iter() {
for referrer in value.referrers.iter() {
Self::commit_change(&mut changes, referrer, params.new_name.clone());
}
}

View file

@ -21,10 +21,11 @@ use erg_compiler::hir::HIR;
use erg_compiler::module::{SharedCompilerResource, SharedModuleIndex};
use lsp_types::{
ClientCapabilities, CompletionOptions, HoverProviderCapability, InitializeResult, OneOf,
ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
CompletionOptions, ExecuteCommandOptions, HoverProviderCapability, InitializeResult, OneOf,
Position, SemanticTokenType, SemanticTokensFullOptions, SemanticTokensLegend,
SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities,
TextDocumentSyncCapability, TextDocumentSyncKind, Url,
TextDocumentSyncCapability, TextDocumentSyncKind, Url, WorkDoneProgressOptions,
};
use crate::hir_visitor::HIRVisitor;
@ -37,19 +38,21 @@ pub type ErgLanguageServer = Server<HIRBuilder>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ELSFeatures {
CodeAction,
Completion,
Diagnostic,
Hover,
SemanticTokens,
Rename,
InlayHint,
FindReferences,
GotoDefinition,
Hover,
InlayHint,
Rename,
SemanticTokens,
}
impl From<&str> for ELSFeatures {
fn from(s: &str) -> Self {
match s {
"codeaction" | "codeAction" | "code-action" => ELSFeatures::CodeAction,
"completion" => ELSFeatures::Completion,
"diagnostic" => ELSFeatures::Diagnostic,
"hover" => ELSFeatures::Hover,
@ -207,6 +210,22 @@ impl<Checker: BuildRunnable> Server<Checker> {
sema_options,
))
};
result.capabilities.code_action_provider = if disabled_features
.contains(&ELSFeatures::CodeAction)
{
None
} else {
let options = CodeActionProviderCapability::Options(CodeActionOptions {
code_action_kinds: Some(vec![CodeActionKind::QUICKFIX, CodeActionKind::REFACTOR]),
resolve_provider: Some(false),
work_done_progress_options: WorkDoneProgressOptions::default(),
});
Some(options)
};
result.capabilities.execute_command_provider = Some(ExecuteCommandOptions {
commands: vec!["erg.eliminate_unused_vars".to_string()],
work_done_progress_options: WorkDoneProgressOptions::default(),
});
Self::send(&json!({
"jsonrpc": "2.0",
"id": id,
@ -319,6 +338,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
"textDocument/references" => self.show_references(msg),
"textDocument/semanticTokens/full" => self.get_semantic_tokens_full(msg),
"textDocument/inlayHint" => self.get_inlay_hint(msg),
"textDocument/codeAction" => self.send_code_action(msg),
other => Self::send_error(Some(id), -32600, format!("{other} is not supported")),
}
}

View file

@ -1,5 +1,5 @@
use std::fs::File;
use std::io::Read;
use std::io::{BufRead, BufReader, Read};
use std::path::Path;
use erg_common::normalize_path;
@ -99,6 +99,36 @@ pub fn get_code_from_uri(uri: &Url) -> ELSResult<String> {
Ok(code)
}
pub fn get_ranged_code_from_uri(uri: &Url, range: Range) -> ELSResult<Option<String>> {
let path = uri.to_file_path().unwrap();
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut code = String::new();
for (i, line) in reader.lines().enumerate() {
if i >= range.start.line as usize && i <= range.end.line as usize {
let line = line?;
if i == range.start.line as usize && i == range.end.line as usize {
if line.len() < range.end.character as usize {
return Ok(None);
}
code.push_str(&line[range.start.character as usize..range.end.character as usize]);
} else if i == range.start.line as usize {
code.push_str(&line[range.start.character as usize..]);
code.push('\n');
} else if i == range.end.line as usize {
if line.len() < range.end.character as usize {
return Ok(None);
}
code.push_str(&line[..range.end.character as usize]);
} else {
code.push_str(&line);
code.push('\n');
}
}
}
Ok(Some(code))
}
pub fn get_line_from_uri(uri: &Url, line: u32) -> ELSResult<String> {
let code = get_code_from_uri(uri)?;
let line = code

View file

@ -971,7 +971,6 @@ impl Context {
self.shared.as_ref().map(|shared| &shared.py_mod_cache)
}
#[cfg(feature = "els")]
pub fn index(&self) -> Option<&crate::module::SharedModuleIndex> {
self.shared.as_ref().map(|shared| &shared.index)
}

View file

@ -3,8 +3,6 @@ use std::option::Option;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
#[cfg(feature = "els")]
use erg_common::config::ErgMode;
use erg_common::env::erg_pystd_path;
use erg_common::levenshtein::get_similar_name;
use erg_common::python_util::BUILTIN_PYTHON_MODS;
@ -109,6 +107,9 @@ impl Context {
py_name,
self.absolutize(ident.name.loc()),
);
if let Some(shared) = self.shared() {
shared.index.register(&vi);
}
self.future_defined_locals.insert(ident.name.clone(), vi);
Ok(())
}
@ -154,6 +155,9 @@ impl Context {
py_name,
self.absolutize(sig.ident.name.loc()),
);
if let Some(shared) = self.shared() {
shared.index.register(&vi);
}
if let Some(_decl) = self.decls.remove(name) {
Err(TyCheckErrors::from(TyCheckError::duplicate_decl_error(
self.cfg.input.clone(),
@ -938,8 +942,11 @@ impl Context {
None,
self.impl_of(),
None,
AbsLocation::unknown(),
self.absolutize(ident.name.loc()),
);
if let Some(shared) = self.shared() {
shared.index.register(&vi);
}
self.decls.insert(ident.name.clone(), vi);
self.consts.insert(ident.name.clone(), other);
}
@ -1074,6 +1081,7 @@ impl Context {
None,
self.impl_of(),
None,
// TODO:
AbsLocation::unknown(),
);
ctx.decls
@ -1113,6 +1121,7 @@ impl Context {
None,
self.impl_of(),
None,
// TODO:
AbsLocation::unknown(),
);
ctx.decls
@ -1160,19 +1169,20 @@ impl Context {
let name = &ident.name;
let muty = Mutability::from(&ident.inspect()[..]);
let id = DefId(get_hash(&(&self.name, &name)));
self.decls.insert(
name.clone(),
VarInfo::new(
Type::Type,
muty,
ident.vis(),
VarKind::Defined(id),
None,
self.impl_of(),
None,
self.absolutize(name.loc()),
),
let vi = VarInfo::new(
Type::Type,
muty,
ident.vis(),
VarKind::Defined(id),
None,
self.impl_of(),
None,
self.absolutize(name.loc()),
);
if let Some(shared) = self.shared() {
shared.index.register(&vi);
}
self.decls.insert(name.clone(), vi);
self.consts
.insert(name.clone(), ValueObj::Type(TypeObj::Builtin(t)));
}
@ -1196,19 +1206,20 @@ impl Context {
let meta_t = gen.meta_type();
let name = &ident.name;
let id = DefId(get_hash(&(&self.name, &name)));
self.decls.insert(
name.clone(),
VarInfo::new(
meta_t,
muty,
ident.vis(),
VarKind::Defined(id),
None,
self.impl_of(),
None,
self.absolutize(name.loc()),
),
let vi = VarInfo::new(
meta_t,
muty,
ident.vis(),
VarKind::Defined(id),
None,
self.impl_of(),
None,
self.absolutize(name.loc()),
);
if let Some(shared) = self.shared() {
shared.index.register(&vi);
}
self.decls.insert(name.clone(), vi);
self.consts
.insert(name.clone(), ValueObj::Type(TypeObj::Generated(gen)));
for impl_trait in ctx.super_traits.iter() {
@ -1624,46 +1635,31 @@ impl Context {
Ok(())
}
#[cfg(feature = "els")]
pub(crate) fn inc_ref_simple_typespec(&self, simple: &SimpleTypeSpec) {
if self.cfg.mode == ErgMode::LanguageServer {
if let Ok(vi) = self.rec_get_var_info(
&simple.ident,
crate::compile::AccessKind::Name,
&self.cfg.input,
&self.name,
) {
self.inc_ref(&vi, &simple.ident.name);
}
if let Ok(vi) = self.rec_get_var_info(
&simple.ident,
crate::compile::AccessKind::Name,
&self.cfg.input,
&self.name,
) {
self.inc_ref(&vi, &simple.ident.name);
}
}
#[cfg(not(feature = "els"))]
pub(crate) fn inc_ref_simple_typespec(&self, _simple: &SimpleTypeSpec) {}
#[cfg(feature = "els")]
pub(crate) fn inc_ref_const_local(&self, local: &ConstIdentifier) {
if self.cfg.mode == ErgMode::LanguageServer {
if let Ok(vi) = self.rec_get_var_info(
local,
crate::compile::AccessKind::Name,
&self.cfg.input,
&self.name,
) {
self.inc_ref(&vi, &local.name);
}
if let Ok(vi) = self.rec_get_var_info(
local,
crate::compile::AccessKind::Name,
&self.cfg.input,
&self.name,
) {
self.inc_ref(&vi, &local.name);
}
}
#[cfg(not(feature = "els"))]
pub(crate) fn inc_ref_const_local(&self, _local: &ConstIdentifier) {}
#[cfg(feature = "els")]
pub fn inc_ref<L: Locational>(&self, vi: &VarInfo, name: &L) {
if self.cfg.mode == ErgMode::LanguageServer {
self.index()
.unwrap()
.add_ref(vi.def_loc.clone(), self.absolutize(name.loc()));
}
self.index()
.unwrap()
.inc_ref(vi, self.absolutize(name.loc()));
}
#[cfg(not(feature = "els"))]
pub fn inc_ref<L: Locational>(&self, _vi: &VarInfo, _name: &L) {}
}

View file

@ -16,6 +16,7 @@ pub mod effectcheck;
pub mod error;
pub mod hir;
pub mod link;
pub mod lint;
pub mod lower;
pub mod module;
pub mod optimize;

160
crates/erg_compiler/lint.rs Normal file
View file

@ -0,0 +1,160 @@
//! Perform basic linting on source code.
//! What is implemented here affects subsequent optimizations,
//! and `erg_linter` does linting that does not affect optimizations.
use erg_common::error::Location;
#[allow(unused_imports)]
use erg_common::log;
use erg_common::traits::{Locational, Runnable, Stream};
use erg_common::Str;
use crate::ty::{HasType, Type};
use crate::error::{LowerError, LowerResult, LowerWarning, LowerWarnings, SingleLowerResult};
use crate::hir;
use crate::lower::ASTLowerer;
use crate::varinfo::VarInfo;
impl ASTLowerer {
pub(crate) fn var_result_t_check(
&self,
loc: Location,
name: &Str,
expect: &Type,
found: &Type,
) -> SingleLowerResult<()> {
self.module
.context
.sub_unify(found, expect, loc, Some(name))
.map_err(|_| {
LowerError::type_mismatch_error(
self.cfg().input.clone(),
line!() as usize,
loc,
self.module.context.caused_by(),
name,
None,
expect,
found,
None, // self.ctx.get_candidates(found),
self.module
.context
.get_simple_type_mismatch_hint(expect, found),
)
})
}
/// OK: exec `i: Int`
/// OK: exec `i: Int = 1`
/// NG: exec `1 + 2`
/// OK: exec `None`
pub(crate) fn use_check(&self, expr: &hir::Expr, mode: &str) -> LowerResult<()> {
if mode != "eval"
&& !expr.ref_t().is_nonelike()
&& !expr.is_type_asc()
&& !expr.is_doc_comment()
{
Err(LowerWarnings::from(LowerWarning::unused_expr_warning(
self.cfg().input.clone(),
line!() as usize,
expr,
String::from(&self.module.context.name[..]),
)))
} else {
self.block_use_check(expr, mode)
}
}
fn block_use_check(&self, expr: &hir::Expr, mode: &str) -> LowerResult<()> {
let mut warns = LowerWarnings::empty();
match expr {
hir::Expr::Def(def) => {
let last = def.body.block.len() - 1;
for (i, chunk) in def.body.block.iter().enumerate() {
if i == last {
if let Err(ws) = self.block_use_check(chunk, mode) {
warns.extend(ws);
}
break;
}
if let Err(ws) = self.use_check(chunk, mode) {
warns.extend(ws);
}
}
}
hir::Expr::Lambda(lambda) => {
let last = lambda.body.len() - 1;
for (i, chunk) in lambda.body.iter().enumerate() {
if i == last {
if let Err(ws) = self.block_use_check(chunk, mode) {
warns.extend(ws);
}
break;
}
if let Err(ws) = self.use_check(chunk, mode) {
warns.extend(ws);
}
}
}
hir::Expr::ClassDef(class_def) => {
for chunk in class_def.methods.iter() {
if let Err(ws) = self.use_check(chunk, mode) {
warns.extend(ws);
}
}
}
hir::Expr::PatchDef(patch_def) => {
for chunk in patch_def.methods.iter() {
if let Err(ws) = self.use_check(chunk, mode) {
warns.extend(ws);
}
}
}
hir::Expr::Call(call) => {
for arg in call.args.pos_args.iter() {
if let Err(ws) = self.block_use_check(&arg.expr, mode) {
warns.extend(ws);
}
}
if let Some(var_args) = &call.args.var_args {
if let Err(ws) = self.block_use_check(&var_args.expr, mode) {
warns.extend(ws);
}
}
for arg in call.args.kw_args.iter() {
if let Err(ws) = self.block_use_check(&arg.expr, mode) {
warns.extend(ws);
}
}
}
// TODO: unary, binary, array, ...
_ => {}
}
if warns.is_empty() {
Ok(())
} else {
Err(warns)
}
}
pub(crate) fn inc_ref<L: Locational>(&self, vi: &VarInfo, name: &L) {
self.module.context.inc_ref(vi, name);
}
pub(crate) fn warn_unused_vars(&mut self) {
if let Some(shared) = self.module.context.shared() {
for (referee, value) in shared.index.iter() {
if value.referrers.is_empty() && value.vi.vis.is_private() {
let warn = LowerWarning::unused_warning(
self.input().clone(),
line!() as usize,
referee.loc,
referee.code().as_ref().map(|s| &s[..]).unwrap_or(""),
self.module.context.caused_by(),
);
self.warns.push(warn);
}
}
}
}
}

View file

@ -188,136 +188,6 @@ impl ASTLowerer {
}
}
impl ASTLowerer {
fn var_result_t_check(
&self,
loc: Location,
name: &Str,
expect: &Type,
found: &Type,
) -> SingleLowerResult<()> {
self.module
.context
.sub_unify(found, expect, loc, Some(name))
.map_err(|_| {
LowerError::type_mismatch_error(
self.cfg.input.clone(),
line!() as usize,
loc,
self.module.context.caused_by(),
name,
None,
expect,
found,
None, // self.ctx.get_candidates(found),
self.module
.context
.get_simple_type_mismatch_hint(expect, found),
)
})
}
/// OK: exec `i: Int`
/// OK: exec `i: Int = 1`
/// NG: exec `1 + 2`
/// OK: exec `None`
fn use_check(&self, expr: &hir::Expr, mode: &str) -> LowerResult<()> {
if mode != "eval"
&& !expr.ref_t().is_nonelike()
&& !expr.is_type_asc()
&& !expr.is_doc_comment()
{
Err(LowerWarnings::from(LowerWarning::unused_expr_warning(
self.cfg.input.clone(),
line!() as usize,
expr,
String::from(&self.module.context.name[..]),
)))
} else {
self.block_use_check(expr, mode)
}
}
fn block_use_check(&self, expr: &hir::Expr, mode: &str) -> LowerResult<()> {
let mut warns = LowerWarnings::empty();
match expr {
hir::Expr::Def(def) => {
let last = def.body.block.len() - 1;
for (i, chunk) in def.body.block.iter().enumerate() {
if i == last {
if let Err(ws) = self.block_use_check(chunk, mode) {
warns.extend(ws);
}
break;
}
if let Err(ws) = self.use_check(chunk, mode) {
warns.extend(ws);
}
}
}
hir::Expr::Lambda(lambda) => {
let last = lambda.body.len() - 1;
for (i, chunk) in lambda.body.iter().enumerate() {
if i == last {
if let Err(ws) = self.block_use_check(chunk, mode) {
warns.extend(ws);
}
break;
}
if let Err(ws) = self.use_check(chunk, mode) {
warns.extend(ws);
}
}
}
hir::Expr::ClassDef(class_def) => {
for chunk in class_def.methods.iter() {
if let Err(ws) = self.use_check(chunk, mode) {
warns.extend(ws);
}
}
}
hir::Expr::PatchDef(patch_def) => {
for chunk in patch_def.methods.iter() {
if let Err(ws) = self.use_check(chunk, mode) {
warns.extend(ws);
}
}
}
hir::Expr::Call(call) => {
for arg in call.args.pos_args.iter() {
if let Err(ws) = self.block_use_check(&arg.expr, mode) {
warns.extend(ws);
}
}
if let Some(var_args) = &call.args.var_args {
if let Err(ws) = self.block_use_check(&var_args.expr, mode) {
warns.extend(ws);
}
}
for arg in call.args.kw_args.iter() {
if let Err(ws) = self.block_use_check(&arg.expr, mode) {
warns.extend(ws);
}
}
}
// TODO: unary, binary, array, ...
_ => {}
}
if warns.is_empty() {
Ok(())
} else {
Err(warns)
}
}
#[cfg(feature = "els")]
fn inc_ref<L: Locational>(&self, vi: &VarInfo, name: &L) {
self.module.context.inc_ref(vi, name);
}
#[cfg(not(feature = "els"))]
fn inc_ref<L: Locational>(&self, _vi: &VarInfo, _name: &L) {}
}
impl ASTLowerer {
pub(crate) fn lower_literal(&self, lit: ast::Literal) -> LowerResult<hir::Literal> {
let loc = lit.loc();
@ -2278,6 +2148,7 @@ impl ASTLowerer {
self.warns.extend(warns);
}
}
self.warn_unused_vars();
if self.errs.is_empty() {
log!(info "the AST lowering process has completed.");
Ok(CompleteArtifact::new(

View file

@ -2,14 +2,12 @@ use erg_common::config::ErgConfig;
use super::cache::SharedModuleCache;
use super::graph::SharedModuleGraph;
#[cfg(feature = "els")]
use super::index::SharedModuleIndex;
#[derive(Debug, Clone, Default)]
pub struct SharedCompilerResource {
pub mod_cache: SharedModuleCache,
pub py_mod_cache: SharedModuleCache,
#[cfg(feature = "els")]
pub index: SharedModuleIndex,
pub graph: SharedModuleGraph,
}
@ -19,7 +17,6 @@ impl SharedCompilerResource {
Self {
mod_cache: SharedModuleCache::new(cfg.copy()),
py_mod_cache: SharedModuleCache::new(cfg),
#[cfg(feature = "els")]
index: SharedModuleIndex::new(),
graph: SharedModuleGraph::new(),
}
@ -28,7 +25,6 @@ impl SharedCompilerResource {
pub fn clear_all(&self) {
self.mod_cache.initialize();
self.py_mod_cache.initialize();
#[cfg(feature = "els")]
self.index.initialize();
self.graph.initialize();
}

View file

@ -1,4 +1,4 @@
use std::collections::hash_map::{Keys, Values};
use std::collections::hash_map::{Iter, Keys, Values};
use std::fmt;
use erg_common::dict::Dict;
@ -6,34 +6,66 @@ use erg_common::set;
use erg_common::set::Set;
use erg_common::shared::Shared;
use crate::varinfo::AbsLocation;
use crate::varinfo::{AbsLocation, VarInfo};
#[derive(Debug, Clone, Default)]
pub struct ModuleIndexValue {
pub vi: VarInfo,
pub referrers: Set<AbsLocation>,
}
impl fmt::Display for ModuleIndexValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{ vi: {}, referrers: {} }}", self.vi, self.referrers)
}
}
impl ModuleIndexValue {
pub const fn new(vi: VarInfo, referrers: Set<AbsLocation>) -> Self {
Self { vi, referrers }
}
pub fn push_ref(&mut self, referrer: AbsLocation) {
self.referrers.insert(referrer);
}
}
#[derive(Debug, Clone, Default)]
pub struct ModuleIndex {
attrs: Dict<AbsLocation, Set<AbsLocation>>,
members: Dict<AbsLocation, ModuleIndexValue>,
}
impl fmt::Display for ModuleIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.attrs.fmt(f)
self.members.fmt(f)
}
}
impl ModuleIndex {
pub fn new() -> Self {
Self { attrs: Dict::new() }
}
pub fn add_ref(&mut self, referee: AbsLocation, referrer: AbsLocation) {
if let Some(referrers) = self.attrs.get_mut(&referee) {
referrers.insert(referrer);
} else {
self.attrs.insert(referee, set! {referrer});
Self {
members: Dict::new(),
}
}
pub fn get_refs(&self, referee: &AbsLocation) -> Option<&Set<AbsLocation>> {
self.attrs.get(referee)
pub fn inc_ref(&mut self, vi: &VarInfo, referrer: AbsLocation) {
let referee = vi.def_loc.clone();
if let Some(referrers) = self.members.get_mut(&referee) {
referrers.push_ref(referrer);
} else {
let value = ModuleIndexValue::new(vi.clone(), set! {referrer});
self.members.insert(referee, value);
}
}
pub fn register(&mut self, vi: &VarInfo) {
let referee = vi.def_loc.clone();
let value = ModuleIndexValue::new(vi.clone(), set! {});
self.members.insert(referee, value);
}
pub fn get_refs(&self, referee: &AbsLocation) -> Option<&ModuleIndexValue> {
self.members.get(referee)
}
}
@ -51,23 +83,31 @@ impl SharedModuleIndex {
Self(Shared::new(ModuleIndex::new()))
}
pub fn add_ref(&self, referee: AbsLocation, referrer: AbsLocation) {
self.0.borrow_mut().add_ref(referee, referrer);
pub fn inc_ref(&self, vi: &VarInfo, referrer: AbsLocation) {
self.0.borrow_mut().inc_ref(vi, referrer);
}
pub fn get_refs(&self, referee: &AbsLocation) -> Option<&Set<AbsLocation>> {
pub fn register(&self, vi: &VarInfo) {
self.0.borrow_mut().register(vi);
}
pub fn get_refs(&self, referee: &AbsLocation) -> Option<&ModuleIndexValue> {
unsafe { self.0.as_ptr().as_ref().unwrap().get_refs(referee) }
}
pub fn referees(&self) -> Keys<AbsLocation, Set<AbsLocation>> {
unsafe { self.0.as_ptr().as_ref().unwrap().attrs.keys() }
pub fn referees(&self) -> Keys<AbsLocation, ModuleIndexValue> {
unsafe { self.0.as_ptr().as_ref().unwrap().members.keys() }
}
pub fn referrers(&self) -> Values<AbsLocation, Set<AbsLocation>> {
unsafe { self.0.as_ptr().as_ref().unwrap().attrs.values() }
pub fn referrers(&self) -> Values<AbsLocation, ModuleIndexValue> {
unsafe { self.0.as_ptr().as_ref().unwrap().members.values() }
}
pub fn iter(&self) -> Iter<AbsLocation, ModuleIndexValue> {
unsafe { self.0.as_ptr().as_ref().unwrap().members.iter() }
}
pub fn initialize(&self) {
self.0.borrow_mut().attrs.clear();
self.0.borrow_mut().members.clear();
}
}

View file

@ -95,6 +95,24 @@ impl AbsLocation {
pub const fn unknown() -> Self {
Self::new(None, Location::Unknown)
}
pub fn code(&self) -> Option<String> {
use std::io::{BufRead, BufReader};
self.module.as_ref().and_then(|module| {
let file = std::fs::File::open(module).unwrap();
let reader = BufReader::new(file);
reader
.lines()
.nth(self.loc.ln_begin().map(|l| l - 1).unwrap_or(0) as usize)
.and_then(|res| {
let res = res.ok()?;
let begin = self.loc.col_begin().unwrap_or(0) as usize;
let end = self.loc.col_end().unwrap_or(0) as usize;
let res = res[begin..end].to_string();
Some(res)
})
})
}
}
/// Has information about the type, variability, visibility, and where the variable was defined (or declared, generated)