mirror of
https://github.com/erg-lang/erg.git
synced 2025-10-02 13:41:10 +00:00
Merge pull request #389 from erg-lang/feat-unused-warn
Add warnings for unused vars
This commit is contained in:
commit
ce2a17a2cd
16 changed files with 545 additions and 228 deletions
118
crates/els/code_action.rs
Normal file
118
crates/els/code_action.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
mod code_action;
|
||||
mod completion;
|
||||
mod definition;
|
||||
mod diagnostics;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
mod code_action;
|
||||
mod completion;
|
||||
mod definition;
|
||||
mod diagnostics;
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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
160
crates/erg_compiler/lint.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue