mirror of https://github.com/mtshiba/pylyzer
feat: use union types for variable defined in if-else
This commit is contained in:
parent
64169a3962
commit
a7801eb145
|
|
@ -145,9 +145,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
|||
|
||||
[[package]]
|
||||
name = "els"
|
||||
version = "0.1.59-nightly.2"
|
||||
version = "0.1.59-nightly.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2f90c210a0919808e48b96ecffd370ac788386ab061203132872e9bf1ad9f7f"
|
||||
checksum = "4b4e110537afd761192ab9cbea35f98d2e1421c1d98520a7c7114d30805ebe8d"
|
||||
dependencies = [
|
||||
"erg_common",
|
||||
"erg_compiler",
|
||||
|
|
@ -161,9 +161,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "erg_common"
|
||||
version = "0.6.47-nightly.2"
|
||||
version = "0.6.47-nightly.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d41f171eb77bf2763b119893966358ad9da72a3edd43cf278a78cf1c16daa2cf"
|
||||
checksum = "c17933aa843a988726e412295f030a501e1bf4c5c8ed1fa4bb5013a37bce9d8a"
|
||||
dependencies = [
|
||||
"backtrace-on-stack-overflow",
|
||||
"erg_proc_macros",
|
||||
|
|
@ -174,9 +174,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "erg_compiler"
|
||||
version = "0.6.47-nightly.2"
|
||||
version = "0.6.47-nightly.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93d0486bc668c120faf7af954dd4046224185c28821fb945a97eedcadf3e7d58"
|
||||
checksum = "d3a10f7dd0644d0378bf1ba45cd923d774552757ac6f19d5f9dee090f0ac668b"
|
||||
dependencies = [
|
||||
"erg_common",
|
||||
"erg_parser",
|
||||
|
|
@ -184,9 +184,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "erg_parser"
|
||||
version = "0.6.47-nightly.2"
|
||||
version = "0.6.47-nightly.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98c37f58f3aef2e765610e7281ada15dbba707beaa0262a71e7f6958ee058ed0"
|
||||
checksum = "8ca055f88af21585c301b93874cff818477c26e0343cb1dfe3b4c69c928bfe31"
|
||||
dependencies = [
|
||||
"erg_common",
|
||||
"erg_proc_macros",
|
||||
|
|
@ -195,9 +195,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "erg_proc_macros"
|
||||
version = "0.6.47-nightly.2"
|
||||
version = "0.6.47-nightly.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fa545f626fd04abea193a07c364c4fca3903c228bbe9cca4895500944b5aaf"
|
||||
checksum = "8560abd2df3c3db6183e7495384f0c1d04d42e779bc0e1402589ab488ab0aaec"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ edition = "2021"
|
|||
repository = "https://github.com/mtshiba/pylyzer"
|
||||
|
||||
[workspace.dependencies]
|
||||
erg_common = { version = "0.6.47-nightly.2", features = ["py_compat", "els"] }
|
||||
erg_compiler = { version = "0.6.47-nightly.2", features = ["py_compat", "els"] }
|
||||
els = { version = "0.1.59-nightly.2", features = ["py_compat"] }
|
||||
erg_common = { version = "0.6.47-nightly.4", features = ["py_compat", "els"] }
|
||||
erg_compiler = { version = "0.6.47-nightly.4", features = ["py_compat", "els"] }
|
||||
els = { version = "0.1.59-nightly.4", features = ["py_compat"] }
|
||||
# rustpython-parser = { version = "0.3.0", features = ["all-nodes-with-ranges", "location"] }
|
||||
# rustpython-ast = { version = "0.3.0", features = ["all-nodes-with-ranges", "location"] }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/Parser", version = "0.4.0", features = ["all-nodes-with-ranges", "location"] }
|
||||
|
|
|
|||
|
|
@ -61,18 +61,10 @@ macro_rules! global_binary_collections {
|
|||
pub const ARROW: Token = Token::dummy(TokenKind::FuncArrow, "->");
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CanShadow {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
impl CanShadow {
|
||||
pub const fn is_yes(&self) -> bool {
|
||||
matches!(self, Self::Yes)
|
||||
}
|
||||
pub const fn is_no(&self) -> bool {
|
||||
matches!(self, Self::No)
|
||||
}
|
||||
pub enum RenameKind {
|
||||
Let,
|
||||
Phi,
|
||||
Redef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
|
@ -97,6 +89,10 @@ impl NameKind {
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum BlockKind {
|
||||
If,
|
||||
/// else, except, finally
|
||||
Else {
|
||||
if_block_id: usize,
|
||||
},
|
||||
For,
|
||||
While,
|
||||
Try,
|
||||
|
|
@ -117,6 +113,15 @@ impl BlockKind {
|
|||
pub const fn makes_scope(&self) -> bool {
|
||||
matches!(self, Self::Function | Self::AsyncFunction | Self::Class)
|
||||
}
|
||||
pub const fn is_else(&self) -> bool {
|
||||
matches!(self, Self::Else { .. })
|
||||
}
|
||||
pub const fn if_block_id(&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Else { if_block_id } => Some(*if_block_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Variables are automatically rewritten with `py_compat`,
|
||||
|
|
@ -308,6 +313,12 @@ impl TypeVarInfo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BlockInfo {
|
||||
pub id: usize,
|
||||
pub kind: BlockKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalContext {
|
||||
pub name: String,
|
||||
|
|
@ -556,7 +567,7 @@ pub struct ASTConverter {
|
|||
pyi_types: PyiTypeStorage,
|
||||
block_id_counter: usize,
|
||||
/// block != scope (if block doesn't make a scope, for example)
|
||||
block_ids: Vec<usize>,
|
||||
blocks: Vec<BlockInfo>,
|
||||
contexts: Vec<LocalContext>,
|
||||
warns: CompileErrors,
|
||||
errs: CompileErrors,
|
||||
|
|
@ -572,7 +583,10 @@ impl ASTConverter {
|
|||
cfg,
|
||||
comments,
|
||||
block_id_counter: 0,
|
||||
block_ids: vec![0],
|
||||
blocks: vec![BlockInfo {
|
||||
id: 0,
|
||||
kind: BlockKind::Module,
|
||||
}],
|
||||
contexts: vec![LocalContext::new("<module>".into(), BlockKind::Module)],
|
||||
warns: CompileErrors::empty(),
|
||||
errs: CompileErrors::empty(),
|
||||
|
|
@ -631,11 +645,11 @@ impl ASTConverter {
|
|||
}
|
||||
|
||||
fn cur_block_kind(&self) -> BlockKind {
|
||||
self.contexts.last().unwrap().kind
|
||||
self.blocks.last().unwrap().kind
|
||||
}
|
||||
|
||||
fn cur_block_id(&self) -> usize {
|
||||
*self.block_ids.last().unwrap()
|
||||
self.blocks.last().unwrap().id
|
||||
}
|
||||
|
||||
/// foo.bar.baz
|
||||
|
|
@ -660,7 +674,7 @@ impl ASTConverter {
|
|||
&self.contexts.last().unwrap().appeared_type_names
|
||||
}
|
||||
|
||||
fn register_name_info(&mut self, name: &str, kind: NameKind) -> CanShadow {
|
||||
fn register_name_info(&mut self, name: &str, kind: NameKind) -> RenameKind {
|
||||
let cur_namespace = self.cur_namespace();
|
||||
let cur_block_id = self.cur_block_id();
|
||||
let cur_block_kind = self.cur_block_kind();
|
||||
|
|
@ -672,13 +686,18 @@ impl ASTConverter {
|
|||
name_info.defined_in = DefinedPlace::Known(cur_namespace);
|
||||
name_info.defined_times += 1; // 0 -> 1
|
||||
}
|
||||
if cur_block_kind.makes_scope()
|
||||
if cur_block_kind
|
||||
.if_block_id()
|
||||
.is_some_and(|id| id == name_info.defined_block_id)
|
||||
{
|
||||
RenameKind::Phi
|
||||
} else if cur_block_kind.makes_scope()
|
||||
|| name_info.defined_block_id == cur_block_id
|
||||
|| name_info.defined_times == 0
|
||||
{
|
||||
CanShadow::Yes
|
||||
RenameKind::Let
|
||||
} else {
|
||||
CanShadow::No
|
||||
RenameKind::Redef
|
||||
}
|
||||
} else {
|
||||
// In Erg, classes can only be defined in uppercase
|
||||
|
|
@ -691,7 +710,7 @@ impl ASTConverter {
|
|||
let defined_in = DefinedPlace::Known(self.cur_namespace());
|
||||
let info = NameInfo::new(rename, defined_in, self.cur_block_id(), 1);
|
||||
self.define_name(String::from(name), info);
|
||||
CanShadow::Yes
|
||||
RenameKind::Let
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -957,12 +976,15 @@ impl ASTConverter {
|
|||
let (param, block) = self.convert_opt_expr_to_param(lhs);
|
||||
let params = Params::new(vec![param], None, vec![], None, None);
|
||||
self.block_id_counter += 1;
|
||||
self.block_ids.push(self.block_id_counter);
|
||||
self.blocks.push(BlockInfo {
|
||||
id: self.block_id_counter,
|
||||
kind: BlockKind::For,
|
||||
});
|
||||
let body = body
|
||||
.into_iter()
|
||||
.map(|stmt| self.convert_statement(stmt, true))
|
||||
.collect::<Vec<_>>();
|
||||
self.block_ids.pop();
|
||||
self.blocks.pop();
|
||||
let body = block.into_iter().chain(body).collect();
|
||||
let sig = LambdaSignature::new(params, None, TypeBoundSpecs::empty());
|
||||
let op = Token::from_str(TokenKind::FuncArrow, "->");
|
||||
|
|
@ -1926,12 +1948,15 @@ impl ASTConverter {
|
|||
let mut new_block = Vec::new();
|
||||
let len = block.len();
|
||||
self.block_id_counter += 1;
|
||||
self.block_ids.push(self.block_id_counter);
|
||||
self.blocks.push(BlockInfo {
|
||||
id: self.block_id_counter,
|
||||
kind,
|
||||
});
|
||||
for (i, stmt) in block.into_iter().enumerate() {
|
||||
let is_last = i == len - 1;
|
||||
new_block.push(self.convert_statement(stmt, is_last && kind.is_function()));
|
||||
}
|
||||
self.block_ids.pop();
|
||||
self.blocks.pop();
|
||||
Block::new(new_block)
|
||||
}
|
||||
|
||||
|
|
@ -2483,17 +2508,38 @@ impl ASTConverter {
|
|||
match *ann_assign.target {
|
||||
py_ast::Expr::Name(name) => {
|
||||
if let Some(value) = ann_assign.value {
|
||||
let block = Block::new(vec![self.convert_expr(*value)]);
|
||||
let body = DefBody::new(EQUAL, block, DefId(0));
|
||||
let expr = self.convert_expr(*value);
|
||||
// must register after convert_expr because value may be contain name (e.g. i = i + 1)
|
||||
let rename =
|
||||
self.register_name_info(name.id.as_str(), NameKind::Variable);
|
||||
let ident = self.convert_ident(name.id.to_string(), name.location());
|
||||
match rename {
|
||||
RenameKind::Let => {
|
||||
let block = Block::new(vec![expr]);
|
||||
let body = DefBody::new(EQUAL, block, DefId(0));
|
||||
let sig = Signature::Var(VarSignature::new(
|
||||
VarPattern::Ident(ident),
|
||||
Some(t_spec),
|
||||
));
|
||||
let def = Def::new(sig, body);
|
||||
Expr::Def(def)
|
||||
}
|
||||
RenameKind::Phi => {
|
||||
let block = Block::new(vec![expr]);
|
||||
let body = DefBody::new(EQUAL, block, DefId(0));
|
||||
let sig = Signature::Var(VarSignature::new(
|
||||
VarPattern::Phi(ident),
|
||||
Some(t_spec),
|
||||
));
|
||||
let def = Def::new(sig, body);
|
||||
Expr::Def(def)
|
||||
}
|
||||
RenameKind::Redef => {
|
||||
let redef =
|
||||
ReDef::new(Accessor::Ident(ident), Some(t_spec), expr);
|
||||
Expr::ReDef(redef)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no registration because it's just a type ascription
|
||||
let ident = self.convert_ident(name.id.to_string(), name.location());
|
||||
|
|
@ -2557,10 +2603,11 @@ impl ASTConverter {
|
|||
self.define_type_var(name.id.to_string(), info);
|
||||
}
|
||||
}
|
||||
let can_shadow = self.register_name_info(&name.id, NameKind::Variable);
|
||||
let rename = self.register_name_info(&name.id, NameKind::Variable);
|
||||
let ident = self.convert_ident(name.id.to_string(), name.location());
|
||||
let t_spec = self.get_assign_t_spec(&name, &expr);
|
||||
if can_shadow.is_yes() {
|
||||
match rename {
|
||||
RenameKind::Let => {
|
||||
let block = Block::new(vec![expr]);
|
||||
let body = DefBody::new(EQUAL, block, DefId(0));
|
||||
let sig = Signature::Var(VarSignature::new(
|
||||
|
|
@ -2569,11 +2616,23 @@ impl ASTConverter {
|
|||
));
|
||||
let def = Def::new(sig, body);
|
||||
Expr::Def(def)
|
||||
} else {
|
||||
}
|
||||
RenameKind::Phi => {
|
||||
let block = Block::new(vec![expr]);
|
||||
let body = DefBody::new(EQUAL, block, DefId(0));
|
||||
let sig = Signature::Var(VarSignature::new(
|
||||
VarPattern::Phi(ident),
|
||||
t_spec,
|
||||
));
|
||||
let def = Def::new(sig, body);
|
||||
Expr::Def(def)
|
||||
}
|
||||
RenameKind::Redef => {
|
||||
let redef = ReDef::new(Accessor::Ident(ident), t_spec, expr);
|
||||
Expr::ReDef(redef)
|
||||
}
|
||||
}
|
||||
}
|
||||
py_ast::Expr::Attribute(attr) => {
|
||||
let value = self.convert_expr(*attr.value);
|
||||
let ident = self
|
||||
|
|
@ -2654,15 +2713,32 @@ impl ASTConverter {
|
|||
py_ast::Expr::Name(name) => {
|
||||
let body =
|
||||
DefBody::new(EQUAL, Block::new(vec![value.clone()]), DefId(0));
|
||||
self.register_name_info(&name.id, NameKind::Variable);
|
||||
let rename = self.register_name_info(&name.id, NameKind::Variable);
|
||||
let ident =
|
||||
self.convert_ident(name.id.to_string(), name.location());
|
||||
match rename {
|
||||
RenameKind::Let => {
|
||||
let sig = Signature::Var(VarSignature::new(
|
||||
VarPattern::Ident(ident),
|
||||
None,
|
||||
));
|
||||
let def = Expr::Def(Def::new(sig, body));
|
||||
defs.push(def);
|
||||
let def = Def::new(sig, body);
|
||||
defs.push(Expr::Def(def));
|
||||
}
|
||||
RenameKind::Phi => {
|
||||
let sig = Signature::Var(VarSignature::new(
|
||||
VarPattern::Phi(ident),
|
||||
None,
|
||||
));
|
||||
let def = Def::new(sig, body);
|
||||
defs.push(Expr::Def(def));
|
||||
}
|
||||
RenameKind::Redef => {
|
||||
let redef =
|
||||
ReDef::new(Accessor::Ident(ident), None, value.clone());
|
||||
defs.push(Expr::ReDef(redef));
|
||||
}
|
||||
}
|
||||
}
|
||||
_other => {
|
||||
defs.push(Expr::Dummy(Dummy::new(None, vec![])));
|
||||
|
|
@ -2770,6 +2846,7 @@ impl ASTConverter {
|
|||
}
|
||||
py_ast::Stmt::If(if_) => {
|
||||
let loc = if_.location();
|
||||
let if_block_id = self.block_id_counter + 1;
|
||||
let block = self.convert_block(if_.body, BlockKind::If);
|
||||
let params = Params::empty();
|
||||
let sig = LambdaSignature::new(params.clone(), None, TypeBoundSpecs::empty());
|
||||
|
|
@ -2778,7 +2855,8 @@ impl ASTConverter {
|
|||
let if_ident = self.convert_ident("if".to_string(), loc);
|
||||
let if_acc = Expr::Accessor(Accessor::Ident(if_ident));
|
||||
if !if_.orelse.is_empty() {
|
||||
let else_block = self.convert_block(if_.orelse, BlockKind::If);
|
||||
let else_block =
|
||||
self.convert_block(if_.orelse, BlockKind::Else { if_block_id });
|
||||
let sig = LambdaSignature::new(params, None, TypeBoundSpecs::empty());
|
||||
let else_body = Lambda::new(sig, Token::DUMMY, else_block, DefId(0));
|
||||
let args = Args::pos_only(
|
||||
|
|
@ -2879,10 +2957,11 @@ impl ASTConverter {
|
|||
self.convert_from_import(import_from.module, import_from.names, loc)
|
||||
}
|
||||
py_ast::Stmt::Try(try_) => {
|
||||
let if_block_id = self.block_id_counter + 1;
|
||||
let chunks = self.convert_block(try_.body, BlockKind::Try).into_iter();
|
||||
let dummy = chunks
|
||||
.chain(self.convert_block(try_.orelse, BlockKind::Try))
|
||||
.chain(self.convert_block(try_.finalbody, BlockKind::Try))
|
||||
.chain(self.convert_block(try_.orelse, BlockKind::Else { if_block_id }))
|
||||
.chain(self.convert_block(try_.finalbody, BlockKind::Else { if_block_id }))
|
||||
.collect();
|
||||
Expr::Dummy(Dummy::new(None, dummy))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Literal
|
||||
|
||||
i: int = 0
|
||||
i: str = "a" # OK
|
||||
|
||||
|
|
@ -37,3 +39,24 @@ if True:
|
|||
left, right = 1, 2
|
||||
if True:
|
||||
left, _ = 1, 2
|
||||
|
||||
def func(label: str) -> str:
|
||||
if True:
|
||||
try:
|
||||
label_bytes = "aaa"
|
||||
except UnicodeEncodeError:
|
||||
return label
|
||||
else:
|
||||
label_bytes = label
|
||||
|
||||
if True:
|
||||
label_bytes = label_bytes[1:]
|
||||
return label_bytes
|
||||
|
||||
if True:
|
||||
y = 1
|
||||
else:
|
||||
y = "a"
|
||||
y: int | str
|
||||
y: Literal[1, "a"] # OK
|
||||
y: Literal[1, "b"] # ERR
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ fn exec_decl() -> Result<(), String> {
|
|||
|
||||
#[test]
|
||||
fn exec_shadowing() -> Result<(), String> {
|
||||
expect("tests/shadowing.py", 0, 3)
|
||||
expect("tests/shadowing.py", 0, 4)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue