feat: use union types for variable defined in if-else

This commit is contained in:
Shunsuke Shibayama 2024-10-20 15:31:49 +09:00
parent 64169a3962
commit a7801eb145
5 changed files with 173 additions and 71 deletions

20
Cargo.lock generated
View File

@ -145,9 +145,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "els" name = "els"
version = "0.1.59-nightly.2" version = "0.1.59-nightly.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2f90c210a0919808e48b96ecffd370ac788386ab061203132872e9bf1ad9f7f" checksum = "4b4e110537afd761192ab9cbea35f98d2e1421c1d98520a7c7114d30805ebe8d"
dependencies = [ dependencies = [
"erg_common", "erg_common",
"erg_compiler", "erg_compiler",
@ -161,9 +161,9 @@ dependencies = [
[[package]] [[package]]
name = "erg_common" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d41f171eb77bf2763b119893966358ad9da72a3edd43cf278a78cf1c16daa2cf" checksum = "c17933aa843a988726e412295f030a501e1bf4c5c8ed1fa4bb5013a37bce9d8a"
dependencies = [ dependencies = [
"backtrace-on-stack-overflow", "backtrace-on-stack-overflow",
"erg_proc_macros", "erg_proc_macros",
@ -174,9 +174,9 @@ dependencies = [
[[package]] [[package]]
name = "erg_compiler" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d0486bc668c120faf7af954dd4046224185c28821fb945a97eedcadf3e7d58" checksum = "d3a10f7dd0644d0378bf1ba45cd923d774552757ac6f19d5f9dee090f0ac668b"
dependencies = [ dependencies = [
"erg_common", "erg_common",
"erg_parser", "erg_parser",
@ -184,9 +184,9 @@ dependencies = [
[[package]] [[package]]
name = "erg_parser" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98c37f58f3aef2e765610e7281ada15dbba707beaa0262a71e7f6958ee058ed0" checksum = "8ca055f88af21585c301b93874cff818477c26e0343cb1dfe3b4c69c928bfe31"
dependencies = [ dependencies = [
"erg_common", "erg_common",
"erg_proc_macros", "erg_proc_macros",
@ -195,9 +195,9 @@ dependencies = [
[[package]] [[package]]
name = "erg_proc_macros" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fa545f626fd04abea193a07c364c4fca3903c228bbe9cca4895500944b5aaf" checksum = "8560abd2df3c3db6183e7495384f0c1d04d42e779bc0e1402589ab488ab0aaec"
dependencies = [ dependencies = [
"quote", "quote",
"syn 1.0.109", "syn 1.0.109",

View File

@ -24,9 +24,9 @@ edition = "2021"
repository = "https://github.com/mtshiba/pylyzer" repository = "https://github.com/mtshiba/pylyzer"
[workspace.dependencies] [workspace.dependencies]
erg_common = { version = "0.6.47-nightly.2", features = ["py_compat", "els"] } erg_common = { version = "0.6.47-nightly.4", features = ["py_compat", "els"] }
erg_compiler = { version = "0.6.47-nightly.2", features = ["py_compat", "els"] } erg_compiler = { version = "0.6.47-nightly.4", features = ["py_compat", "els"] }
els = { version = "0.1.59-nightly.2", features = ["py_compat"] } els = { version = "0.1.59-nightly.4", features = ["py_compat"] }
# rustpython-parser = { version = "0.3.0", features = ["all-nodes-with-ranges", "location"] } # 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-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"] } rustpython-parser = { git = "https://github.com/RustPython/Parser", version = "0.4.0", features = ["all-nodes-with-ranges", "location"] }

View File

@ -61,18 +61,10 @@ macro_rules! global_binary_collections {
pub const ARROW: Token = Token::dummy(TokenKind::FuncArrow, "->"); pub const ARROW: Token = Token::dummy(TokenKind::FuncArrow, "->");
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CanShadow { pub enum RenameKind {
Yes, Let,
No, Phi,
} Redef,
impl CanShadow {
pub const fn is_yes(&self) -> bool {
matches!(self, Self::Yes)
}
pub const fn is_no(&self) -> bool {
matches!(self, Self::No)
}
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -97,6 +89,10 @@ impl NameKind {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockKind { pub enum BlockKind {
If, If,
/// else, except, finally
Else {
if_block_id: usize,
},
For, For,
While, While,
Try, Try,
@ -117,6 +113,15 @@ impl BlockKind {
pub const fn makes_scope(&self) -> bool { pub const fn makes_scope(&self) -> bool {
matches!(self, Self::Function | Self::AsyncFunction | Self::Class) 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`, /// 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)] #[derive(Debug)]
pub struct LocalContext { pub struct LocalContext {
pub name: String, pub name: String,
@ -556,7 +567,7 @@ pub struct ASTConverter {
pyi_types: PyiTypeStorage, pyi_types: PyiTypeStorage,
block_id_counter: usize, block_id_counter: usize,
/// block != scope (if block doesn't make a scope, for example) /// block != scope (if block doesn't make a scope, for example)
block_ids: Vec<usize>, blocks: Vec<BlockInfo>,
contexts: Vec<LocalContext>, contexts: Vec<LocalContext>,
warns: CompileErrors, warns: CompileErrors,
errs: CompileErrors, errs: CompileErrors,
@ -572,7 +583,10 @@ impl ASTConverter {
cfg, cfg,
comments, comments,
block_id_counter: 0, block_id_counter: 0,
block_ids: vec![0], blocks: vec![BlockInfo {
id: 0,
kind: BlockKind::Module,
}],
contexts: vec![LocalContext::new("<module>".into(), BlockKind::Module)], contexts: vec![LocalContext::new("<module>".into(), BlockKind::Module)],
warns: CompileErrors::empty(), warns: CompileErrors::empty(),
errs: CompileErrors::empty(), errs: CompileErrors::empty(),
@ -631,11 +645,11 @@ impl ASTConverter {
} }
fn cur_block_kind(&self) -> BlockKind { fn cur_block_kind(&self) -> BlockKind {
self.contexts.last().unwrap().kind self.blocks.last().unwrap().kind
} }
fn cur_block_id(&self) -> usize { fn cur_block_id(&self) -> usize {
*self.block_ids.last().unwrap() self.blocks.last().unwrap().id
} }
/// foo.bar.baz /// foo.bar.baz
@ -660,7 +674,7 @@ impl ASTConverter {
&self.contexts.last().unwrap().appeared_type_names &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_namespace = self.cur_namespace();
let cur_block_id = self.cur_block_id(); let cur_block_id = self.cur_block_id();
let cur_block_kind = self.cur_block_kind(); 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_in = DefinedPlace::Known(cur_namespace);
name_info.defined_times += 1; // 0 -> 1 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_block_id == cur_block_id
|| name_info.defined_times == 0 || name_info.defined_times == 0
{ {
CanShadow::Yes RenameKind::Let
} else { } else {
CanShadow::No RenameKind::Redef
} }
} else { } else {
// In Erg, classes can only be defined in uppercase // In Erg, classes can only be defined in uppercase
@ -691,7 +710,7 @@ impl ASTConverter {
let defined_in = DefinedPlace::Known(self.cur_namespace()); let defined_in = DefinedPlace::Known(self.cur_namespace());
let info = NameInfo::new(rename, defined_in, self.cur_block_id(), 1); let info = NameInfo::new(rename, defined_in, self.cur_block_id(), 1);
self.define_name(String::from(name), info); 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 (param, block) = self.convert_opt_expr_to_param(lhs);
let params = Params::new(vec![param], None, vec![], None, None); let params = Params::new(vec![param], None, vec![], None, None);
self.block_id_counter += 1; 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 let body = body
.into_iter() .into_iter()
.map(|stmt| self.convert_statement(stmt, true)) .map(|stmt| self.convert_statement(stmt, true))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.block_ids.pop(); self.blocks.pop();
let body = block.into_iter().chain(body).collect(); let body = block.into_iter().chain(body).collect();
let sig = LambdaSignature::new(params, None, TypeBoundSpecs::empty()); let sig = LambdaSignature::new(params, None, TypeBoundSpecs::empty());
let op = Token::from_str(TokenKind::FuncArrow, "->"); let op = Token::from_str(TokenKind::FuncArrow, "->");
@ -1926,12 +1948,15 @@ impl ASTConverter {
let mut new_block = Vec::new(); let mut new_block = Vec::new();
let len = block.len(); let len = block.len();
self.block_id_counter += 1; 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() { for (i, stmt) in block.into_iter().enumerate() {
let is_last = i == len - 1; let is_last = i == len - 1;
new_block.push(self.convert_statement(stmt, is_last && kind.is_function())); new_block.push(self.convert_statement(stmt, is_last && kind.is_function()));
} }
self.block_ids.pop(); self.blocks.pop();
Block::new(new_block) Block::new(new_block)
} }
@ -2483,17 +2508,38 @@ impl ASTConverter {
match *ann_assign.target { match *ann_assign.target {
py_ast::Expr::Name(name) => { py_ast::Expr::Name(name) => {
if let Some(value) = ann_assign.value { if let Some(value) = ann_assign.value {
let block = Block::new(vec![self.convert_expr(*value)]); let expr = self.convert_expr(*value);
let body = DefBody::new(EQUAL, block, DefId(0));
// must register after convert_expr because value may be contain name (e.g. i = i + 1) // must register after convert_expr because value may be contain name (e.g. i = i + 1)
self.register_name_info(name.id.as_str(), NameKind::Variable); let rename =
self.register_name_info(name.id.as_str(), NameKind::Variable);
let ident = self.convert_ident(name.id.to_string(), name.location()); let ident = self.convert_ident(name.id.to_string(), name.location());
let sig = Signature::Var(VarSignature::new( match rename {
VarPattern::Ident(ident), RenameKind::Let => {
Some(t_spec), let block = Block::new(vec![expr]);
)); let body = DefBody::new(EQUAL, block, DefId(0));
let def = Def::new(sig, body); let sig = Signature::Var(VarSignature::new(
Expr::Def(def) 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 { } else {
// no registration because it's just a type ascription // no registration because it's just a type ascription
let ident = self.convert_ident(name.id.to_string(), name.location()); let ident = self.convert_ident(name.id.to_string(), name.location());
@ -2557,21 +2603,34 @@ impl ASTConverter {
self.define_type_var(name.id.to_string(), info); 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 ident = self.convert_ident(name.id.to_string(), name.location());
let t_spec = self.get_assign_t_spec(&name, &expr); let t_spec = self.get_assign_t_spec(&name, &expr);
if can_shadow.is_yes() { match rename {
let block = Block::new(vec![expr]); RenameKind::Let => {
let body = DefBody::new(EQUAL, block, DefId(0)); let block = Block::new(vec![expr]);
let sig = Signature::Var(VarSignature::new( let body = DefBody::new(EQUAL, block, DefId(0));
VarPattern::Ident(ident), let sig = Signature::Var(VarSignature::new(
t_spec, VarPattern::Ident(ident),
)); t_spec,
let def = Def::new(sig, body); ));
Expr::Def(def) let def = Def::new(sig, body);
} else { Expr::Def(def)
let redef = ReDef::new(Accessor::Ident(ident), t_spec, expr); }
Expr::ReDef(redef) 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) => { py_ast::Expr::Attribute(attr) => {
@ -2654,15 +2713,32 @@ impl ASTConverter {
py_ast::Expr::Name(name) => { py_ast::Expr::Name(name) => {
let body = let body =
DefBody::new(EQUAL, Block::new(vec![value.clone()]), DefId(0)); 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 = let ident =
self.convert_ident(name.id.to_string(), name.location()); self.convert_ident(name.id.to_string(), name.location());
let sig = Signature::Var(VarSignature::new( match rename {
VarPattern::Ident(ident), RenameKind::Let => {
None, let sig = Signature::Var(VarSignature::new(
)); VarPattern::Ident(ident),
let def = Expr::Def(Def::new(sig, body)); None,
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 => { _other => {
defs.push(Expr::Dummy(Dummy::new(None, vec![]))); defs.push(Expr::Dummy(Dummy::new(None, vec![])));
@ -2770,6 +2846,7 @@ impl ASTConverter {
} }
py_ast::Stmt::If(if_) => { py_ast::Stmt::If(if_) => {
let loc = if_.location(); let loc = if_.location();
let if_block_id = self.block_id_counter + 1;
let block = self.convert_block(if_.body, BlockKind::If); let block = self.convert_block(if_.body, BlockKind::If);
let params = Params::empty(); let params = Params::empty();
let sig = LambdaSignature::new(params.clone(), None, TypeBoundSpecs::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_ident = self.convert_ident("if".to_string(), loc);
let if_acc = Expr::Accessor(Accessor::Ident(if_ident)); let if_acc = Expr::Accessor(Accessor::Ident(if_ident));
if !if_.orelse.is_empty() { 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 sig = LambdaSignature::new(params, None, TypeBoundSpecs::empty());
let else_body = Lambda::new(sig, Token::DUMMY, else_block, DefId(0)); let else_body = Lambda::new(sig, Token::DUMMY, else_block, DefId(0));
let args = Args::pos_only( let args = Args::pos_only(
@ -2879,10 +2957,11 @@ impl ASTConverter {
self.convert_from_import(import_from.module, import_from.names, loc) self.convert_from_import(import_from.module, import_from.names, loc)
} }
py_ast::Stmt::Try(try_) => { 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 chunks = self.convert_block(try_.body, BlockKind::Try).into_iter();
let dummy = chunks let dummy = chunks
.chain(self.convert_block(try_.orelse, BlockKind::Try)) .chain(self.convert_block(try_.orelse, BlockKind::Else { if_block_id }))
.chain(self.convert_block(try_.finalbody, BlockKind::Try)) .chain(self.convert_block(try_.finalbody, BlockKind::Else { if_block_id }))
.collect(); .collect();
Expr::Dummy(Dummy::new(None, dummy)) Expr::Dummy(Dummy::new(None, dummy))
} }

View File

@ -1,3 +1,5 @@
from typing import Literal
i: int = 0 i: int = 0
i: str = "a" # OK i: str = "a" # OK
@ -37,3 +39,24 @@ if True:
left, right = 1, 2 left, right = 1, 2
if True: if True:
left, _ = 1, 2 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

View File

@ -160,7 +160,7 @@ fn exec_decl() -> Result<(), String> {
#[test] #[test]
fn exec_shadowing() -> Result<(), String> { fn exec_shadowing() -> Result<(), String> {
expect("tests/shadowing.py", 0, 3) expect("tests/shadowing.py", 0, 4)
} }
#[test] #[test]