mirror of https://github.com/mtshiba/pylyzer
Add tests
This commit is contained in:
parent
eb3fc9be8d
commit
f46b33d243
|
|
@ -227,7 +227,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erg_common"
|
name = "erg_common"
|
||||||
version = "0.6.0-beta.3"
|
version = "0.6.0-beta.3"
|
||||||
source = "git+https://github.com/erg-lang/erg?branch=main#da2ff544f9cc4f6b1f875c24e513599d5d7fc4f7"
|
source = "git+https://github.com/erg-lang/erg?branch=main#9c83663c52479420dc9df4c46a780820f1364fda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -237,7 +237,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erg_compiler"
|
name = "erg_compiler"
|
||||||
version = "0.6.0-beta.3"
|
version = "0.6.0-beta.3"
|
||||||
source = "git+https://github.com/erg-lang/erg?branch=main#da2ff544f9cc4f6b1f875c24e513599d5d7fc4f7"
|
source = "git+https://github.com/erg-lang/erg?branch=main#9c83663c52479420dc9df4c46a780820f1364fda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"erg_common",
|
"erg_common",
|
||||||
"erg_parser",
|
"erg_parser",
|
||||||
|
|
@ -246,7 +246,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erg_parser"
|
name = "erg_parser"
|
||||||
version = "0.6.0-beta.3"
|
version = "0.6.0-beta.3"
|
||||||
source = "git+https://github.com/erg-lang/erg?branch=main#da2ff544f9cc4f6b1f875c24e513599d5d7fc4f7"
|
source = "git+https://github.com/erg-lang/erg?branch=main#9c83663c52479420dc9df4c46a780820f1364fda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"erg_common",
|
"erg_common",
|
||||||
"unicode-xid 0.2.4",
|
"unicode-xid 0.2.4",
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,6 @@ erg_compiler = { git = "https://github.com/erg-lang/erg", branch = "main" }
|
||||||
erg_common = { git = "https://github.com/erg-lang/erg", branch = "main" }
|
erg_common = { git = "https://github.com/erg-lang/erg", branch = "main" }
|
||||||
els = { git = "https://github.com/erg-lang/erg-language-server", branch = "main" }
|
els = { git = "https://github.com/erg-lang/erg-language-server", branch = "main" }
|
||||||
py2erg = { path = "./crates/py2erg" }
|
py2erg = { path = "./crates/py2erg" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use erg_compiler::artifact::CompleteArtifact;
|
||||||
use rustpython_parser::ast::{StatementType, ExpressionType, Located, Program, Number, StringGroup, Operator, BooleanOperator, UnaryOperator, Suite, Parameters, Parameter, Comparison};
|
use rustpython_parser::ast::{StatementType, ExpressionType, Located, Program, Number, StringGroup, Operator, BooleanOperator, UnaryOperator, Suite, Parameters, Parameter, Comparison};
|
||||||
use rustpython_parser::ast::Location as PyLocation;
|
use rustpython_parser::ast::Location as PyLocation;
|
||||||
|
|
||||||
|
use erg_common::set;
|
||||||
use erg_common::set::Set as HashSet;
|
use erg_common::set::Set as HashSet;
|
||||||
use erg_common::dict::Dict as HashMap;
|
use erg_common::dict::Dict as HashMap;
|
||||||
use erg_compiler::erg_parser::token::{Token, TokenKind, EQUAL, COLON, DOT};
|
use erg_compiler::erg_parser::token::{Token, TokenKind, EQUAL, COLON, DOT};
|
||||||
|
|
@ -101,57 +102,63 @@ impl NameInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ShadowingMode {
|
||||||
|
Invisible,
|
||||||
|
Visible,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ASTConverter {
|
pub struct ASTConverter {
|
||||||
cfg: ErgConfig,
|
cfg: ErgConfig,
|
||||||
|
shadowing: ShadowingMode,
|
||||||
namespace: Vec<String>,
|
namespace: Vec<String>,
|
||||||
/// Erg does not allow variables to be defined multiple times, so rename them using this
|
/// Erg does not allow variables to be defined multiple times, so rename them using this
|
||||||
names: Vec<HashMap<String, NameInfo>>,
|
names: HashMap<String, NameInfo>,
|
||||||
warns: CompileErrors,
|
warns: CompileErrors,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ASTConverter {
|
impl ASTConverter {
|
||||||
pub fn new(cfg: ErgConfig) -> Self {
|
pub fn new(cfg: ErgConfig, shadowing: ShadowingMode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
shadowing,
|
||||||
cfg,
|
cfg,
|
||||||
namespace: vec![String::from("<module>")],
|
namespace: vec![String::from("<module>")],
|
||||||
names: vec![HashMap::new()],
|
names: HashMap::new(),
|
||||||
warns: CompileErrors::empty(),
|
warns: CompileErrors::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name_global(&self, name: &str) -> Option<&NameInfo> {
|
fn get_name(&self, name: &str) -> Option<&NameInfo> {
|
||||||
for space in self.names.iter().rev() {
|
self.names.get(name)
|
||||||
if let Some(name) = space.get(name) {
|
|
||||||
return Some(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mut_name_global(&mut self, name: &str) -> Option<&mut NameInfo> {
|
fn get_mut_name(&mut self, name: &str) -> Option<&mut NameInfo> {
|
||||||
for space in self.names.iter_mut().rev() {
|
self.names.get_mut(name)
|
||||||
if let Some(name) = space.get_mut(name) {
|
|
||||||
return Some(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_ident(&mut self, name: String, loc: PyLocation) -> Identifier {
|
fn convert_ident(&mut self, name: String, loc: PyLocation) -> Identifier {
|
||||||
|
let shadowing = self.shadowing;
|
||||||
let referrer = self.namespace.last().unwrap().clone();
|
let referrer = self.namespace.last().unwrap().clone();
|
||||||
let name = escape_name(name);
|
let name = escape_name(name);
|
||||||
let cont = if let Some(name_info) = self.get_mut_name_global(&name) {
|
let cont = if let Some(name_info) = self.get_mut_name(&name) {
|
||||||
if referrer != "<module>" {
|
if referrer != "<module>" {
|
||||||
name_info.add_referrer(referrer);
|
name_info.add_referrer(referrer);
|
||||||
}
|
}
|
||||||
if name_info.defined_times > 1 {
|
if name_info.defined_times > 1 {
|
||||||
// HACK: add zero-width characters as postfix
|
if shadowing == ShadowingMode::Invisible {
|
||||||
format!("{name}{}", "\0".repeat(name_info.defined_times))
|
// HACK: add zero-width characters as postfix
|
||||||
|
format!("{name}{}", "\0".repeat(name_info.defined_times))
|
||||||
|
} else {
|
||||||
|
format!("{name}__{}", name_info.defined_times)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let mut info = NameInfo::new(0);
|
||||||
|
info.add_referrer(referrer);
|
||||||
|
self.names.insert(name.clone(), info);
|
||||||
name
|
name
|
||||||
};
|
};
|
||||||
let token = Token::new(TokenKind::Symbol, cont, loc.row(), loc.column() - 1);
|
let token = Token::new(TokenKind::Symbol, cont, loc.row(), loc.column() - 1);
|
||||||
|
|
@ -542,11 +549,11 @@ impl ASTConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_name_info(&mut self, name: &str) {
|
fn register_name_info(&mut self, name: &str) {
|
||||||
if let Some(name_info) = self.names.last_mut().unwrap().get_mut(name) {
|
if let Some(name_info) = self.names.get_mut(name) {
|
||||||
// name_info.last_defined_line = loc.row();
|
// name_info.last_defined_line = loc.row();
|
||||||
name_info.defined_times += 1;
|
name_info.defined_times += 1;
|
||||||
} else {
|
} else {
|
||||||
self.names.last_mut().unwrap().insert(String::from(name), NameInfo::new(1));
|
self.names.insert(String::from(name), NameInfo::new(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -686,10 +693,9 @@ impl ASTConverter {
|
||||||
returns
|
returns
|
||||||
} => {
|
} => {
|
||||||
// if reassigning of a function referenced by other functions is occurred, it is an error
|
// if reassigning of a function referenced by other functions is occurred, it is an error
|
||||||
if self.get_name_global(&name).map(|info| !info.referenced.is_empty()).unwrap_or(false) {
|
if self.get_name(&name).map(|info| info.defined_times > 0 && !info.referenced.difference(&set!{name.clone()}).is_empty()).unwrap_or(false) {
|
||||||
let warn = reassign_func_error(
|
let warn = reassign_func_error(
|
||||||
self.cfg.input.clone(),
|
self.cfg.input.clone(),
|
||||||
line!() as usize,
|
|
||||||
pyloc_to_ergloc(stmt.location, name.len()),
|
pyloc_to_ergloc(stmt.location, name.len()),
|
||||||
self.namespace.join("."),
|
self.namespace.join("."),
|
||||||
&name
|
&name
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use erg_compiler::context::Context;
|
||||||
use erg_compiler::erg_parser::ast::AST;
|
use erg_compiler::erg_parser::ast::AST;
|
||||||
use erg_compiler::error::{CompileErrors, CompileError};
|
use erg_compiler::error::{CompileErrors, CompileError};
|
||||||
use erg_compiler::lower::ASTLowerer;
|
use erg_compiler::lower::ASTLowerer;
|
||||||
|
use py2erg::ShadowingMode;
|
||||||
use py2erg::dump_decl_er;
|
use py2erg::dump_decl_er;
|
||||||
use rustpython_parser::parser;
|
use rustpython_parser::parser;
|
||||||
|
|
||||||
|
|
@ -68,6 +69,10 @@ impl Buildable for PythonAnalyzer {
|
||||||
impl BuildRunnable for PythonAnalyzer {}
|
impl BuildRunnable for PythonAnalyzer {}
|
||||||
|
|
||||||
impl PythonAnalyzer {
|
impl PythonAnalyzer {
|
||||||
|
pub fn new(cfg: ErgConfig) -> Self {
|
||||||
|
Runnable::new(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn analyze(&mut self, py_code: String, mode: &str) -> Result<CompleteArtifact, IncompleteArtifact> {
|
pub fn analyze(&mut self, py_code: String, mode: &str) -> Result<CompleteArtifact, IncompleteArtifact> {
|
||||||
let filename = self.cfg.input.filename();
|
let filename = self.cfg.input.filename();
|
||||||
let py_program = parser::parse_program(&py_code).map_err(|err| {
|
let py_program = parser::parse_program(&py_code).map_err(|err| {
|
||||||
|
|
@ -81,16 +86,22 @@ impl PythonAnalyzer {
|
||||||
let err = CompileError::new(core, self.cfg.input.clone(), "".into());
|
let err = CompileError::new(core, self.cfg.input.clone(), "".into());
|
||||||
IncompleteArtifact::new(None, CompileErrors::from(err), CompileErrors::empty())
|
IncompleteArtifact::new(None, CompileErrors::from(err), CompileErrors::empty())
|
||||||
})?;
|
})?;
|
||||||
let converter = py2erg::ASTConverter::new(self.cfg.copy());
|
let converter = py2erg::ASTConverter::new(self.cfg.copy(), ShadowingMode::Invisible);
|
||||||
let CompleteArtifact{ object: erg_module, mut warns } = converter.convert_program(py_program);
|
let CompleteArtifact{ object: erg_module, mut warns } = converter.convert_program(py_program);
|
||||||
let erg_ast = AST::new(erg_common::Str::rc(filename), erg_module);
|
let erg_ast = AST::new(erg_common::Str::rc(filename), erg_module);
|
||||||
erg_common::log!("AST: {erg_ast}");
|
erg_common::log!("AST: {erg_ast}");
|
||||||
self.checker.lower(erg_ast, mode).map_err(|iart| {
|
match self.checker.lower(erg_ast, mode) {
|
||||||
let errors = handle_err::filter_errors(self.checker.get_mod_ctx(), iart.errors);
|
Ok(mut artifact) => {
|
||||||
let ws = handle_err::filter_errors(self.checker.get_mod_ctx(), iart.warns);
|
artifact.warns.extend(warns);
|
||||||
warns.extend(ws);
|
Ok(artifact)
|
||||||
IncompleteArtifact::new(iart.object, errors, warns)
|
}
|
||||||
})
|
Err(iart) => {
|
||||||
|
let errors = handle_err::filter_errors(self.checker.get_mod_ctx(), iart.errors);
|
||||||
|
let ws = handle_err::filter_errors(self.checker.get_mod_ctx(), iart.warns);
|
||||||
|
warns.extend(ws);
|
||||||
|
Err(IncompleteArtifact::new(iart.object, errors, warns))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
pub fn run(&mut self) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
mod analyze;
|
||||||
|
mod handle_err;
|
||||||
|
|
||||||
|
pub use analyze::PythonAnalyzer;
|
||||||
|
|
@ -9,7 +9,6 @@ use analyze::PythonAnalyzer;
|
||||||
use els::Server;
|
use els::Server;
|
||||||
use erg_common::config::{Input, ErgConfig};
|
use erg_common::config::{Input, ErgConfig};
|
||||||
use erg_common::spawn::exec_new_thread;
|
use erg_common::spawn::exec_new_thread;
|
||||||
use erg_common::traits::Runnable;
|
|
||||||
|
|
||||||
pub fn parse_args() -> ErgConfig {
|
pub fn parse_args() -> ErgConfig {
|
||||||
let mut args = env::args();
|
let mut args = env::args();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
def a(): return 1
|
||||||
|
def a(): return "a" # OK
|
||||||
|
|
||||||
|
print(a())
|
||||||
|
|
||||||
|
def g(): return f()
|
||||||
|
|
||||||
|
def f(): return 1
|
||||||
|
def f(): return "a" # E0001: Reassignment of a function referenced by other functions
|
||||||
|
|
@ -8,4 +8,4 @@ rdi(0, 1, 2) # ERR
|
||||||
|
|
||||||
print(export.test)
|
print(export.test)
|
||||||
print(export.add(1, 2))
|
print(export.add(1, 2))
|
||||||
assert export.add("a", "b") == 1
|
assert export.add("a", "b") == 1 # ERR
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,32 @@
|
||||||
|
|
||||||
def add(x, y):
|
def add(x, y):
|
||||||
return x + y
|
return x + y
|
||||||
|
|
||||||
print(add(1, 2))
|
print(add(1, 2))
|
||||||
print(add(1, "a"))
|
print(add(1, "a")) # ERR
|
||||||
add.x = 1
|
add.x = 1 # ERR
|
||||||
|
|
||||||
def add2(x: int, y: int) -> str:
|
def add2(x: int, y: int) -> str: # ERR
|
||||||
return x + y
|
return x + y
|
||||||
|
|
||||||
print(add2(1, 2))
|
print(add2(1, 2))
|
||||||
|
|
||||||
|
# ERR
|
||||||
for i in [1, 2, 3]:
|
for i in [1, 2, 3]:
|
||||||
j = i + "aa"
|
j = i + "aa"
|
||||||
print(j)
|
print(j)
|
||||||
|
|
||||||
while "aaa":
|
while "aaa": # ERR
|
||||||
print("invalid")
|
print("invalid")
|
||||||
break
|
break
|
||||||
|
|
||||||
class C:
|
class C:
|
||||||
x = 1 + "a"
|
x = 1 + "a" # ERR
|
||||||
|
|
||||||
dic = {"a": 1, "b": 2}
|
dic = {"a": 1, "b": 2}
|
||||||
print(dic["c"])
|
print(dic["c"]) # ERR
|
||||||
|
|
||||||
a = [1, 2, 3]
|
a = [1, 2, 3]
|
||||||
print(a[4])
|
print(a[4]) # ERR
|
||||||
|
|
||||||
a_ = "aa" if True else "bb"
|
a_ = "aa" if True else "bb"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use erg_common::traits::Stream;
|
||||||
|
use erg_common::config::{ErgConfig, Input};
|
||||||
|
use erg_compiler::artifact::{IncompleteArtifact, CompleteArtifact};
|
||||||
|
use pylyzer::PythonAnalyzer;
|
||||||
|
|
||||||
|
pub fn exec_analyzer(file_path: &'static str) -> Result<CompleteArtifact, IncompleteArtifact> {
|
||||||
|
let cfg = ErgConfig { python_compatible_mode: true, input: Input::File(PathBuf::from(file_path)), ..Default::default() };
|
||||||
|
let mut analyzer = PythonAnalyzer::new(cfg);
|
||||||
|
let py_code = analyzer.cfg.input.read();
|
||||||
|
analyzer.analyze(py_code, "exec")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expect(file_path: &'static str, warns: usize, errors: usize) {
|
||||||
|
match exec_analyzer(file_path) {
|
||||||
|
Ok(artifact) => {
|
||||||
|
assert_eq!(artifact.warns.len(), warns);
|
||||||
|
assert_eq!(errors, 0);
|
||||||
|
}
|
||||||
|
Err(artifact) => {
|
||||||
|
assert_eq!(artifact.warns.len(), warns);
|
||||||
|
assert_eq!(artifact.errors.len(), errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_test() {
|
||||||
|
expect("tests/test.py", 0, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_import() {
|
||||||
|
expect("tests/import.py", 0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_export() {
|
||||||
|
expect("tests/export.py", 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_class() {
|
||||||
|
expect("tests/class.py", 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_e0001() {
|
||||||
|
expect("tests/e0001.py", 1, 0);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue