fix: complement type narrowing bug

This commit is contained in:
Shunsuke Shibayama 2025-02-08 15:10:48 +09:00
parent 8043a13644
commit 21582953c0
2 changed files with 40 additions and 22 deletions

View file

@ -1141,35 +1141,45 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
} }
fn get_call_guard_type(&self, call: &ast::Call) -> Option<Type> { fn get_call_guard_type(&self, call: &ast::Call) -> Option<Type> {
if let (ast::Expr::Accessor(ast::Accessor::Ident(ident)), None, Some(lhs), Some(rhs)) = ( match (
call.obj.as_ref(), call.obj.as_ref(),
&call.attr_name, &call.attr_name,
&call.args.nth_or_key(0, "object"), &call.args.nth_or_key(0, "object"),
&call.args.nth_or_key(1, "classinfo"), &call.args.nth_or_key(1, "classinfo"),
) { ) {
match &ident.inspect()[..] { (ast::Expr::Accessor(ast::Accessor::Ident(ident)), None, Some(lhs), Some(rhs)) => {
"isinstance" | "issubclass" => { match &ident.inspect()[..] {
self.get_bin_guard_type(ident.name.token(), lhs, rhs) "isinstance" | "issubclass" => {
} self.get_bin_guard_type(ident.name.token(), lhs, rhs)
"hasattr" => {
let ast::Expr::Literal(lit) = rhs else {
return None;
};
if !lit.is(TokenKind::StrLit) {
return None;
} }
let name = lit.token.content.trim_matches('\"'); "hasattr" => {
let target = self.expr_to_cast_target(lhs); let ast::Expr::Literal(lit) = rhs else {
let rec = dict! { return None;
Field::new(VisibilityModifier::Public, Str::rc(name)) => Type::Obj, };
}; if !lit.is(TokenKind::StrLit) {
let to = Type::Record(rec).structuralize(); return None;
Some(guard(self.module.context.name.clone(), target, to)) }
let name = lit.token.content.trim_matches('\"');
let target = self.expr_to_cast_target(lhs);
let rec = dict! {
Field::new(VisibilityModifier::Public, Str::rc(name)) => Type::Obj,
};
let to = Type::Record(rec).structuralize();
Some(guard(self.module.context.name.clone(), target, to))
}
_ => None,
} }
_ => None,
} }
} else { (ast::Expr::Accessor(ast::Accessor::Ident(ident)), None, Some(arg), None) => {
None match &ident.inspect()[..] {
"not" => {
let arg = self.get_guard_type(arg)?;
Some(self.module.context.complement(&arg))
}
_ => None,
}
}
_ => None,
} }
} }
@ -1428,7 +1438,9 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
for (nth, (arg, param)) in pos_args { for (nth, (arg, param)) in pos_args {
match self.lower_expr(arg.expr, param) { match self.lower_expr(arg.expr, param) {
Ok(expr) => { Ok(expr) => {
if let Some(kind) = self.module.context.control_kind() { // e.g. `if not isinstance(x, Int)`
// push {x in not Int}, not {x in Int}
if let Some(kind) = self.module.context.current_control_flow() {
self.push_guard(nth, kind, expr.ref_t()); self.push_guard(nth, kind, expr.ref_t());
} }
hir_pos_args[nth] = hir::PosArg::new(expr); hir_pos_args[nth] = hir::PosArg::new(expr);

View file

@ -25,3 +25,9 @@ is_int(x: Obj) = x in Int
y as Obj = 1 y as Obj = 1
assert is_int y assert is_int y
y: Int y: Int
bs x: Bytes or Str =
discard if not(isinstance(x, Str)), do:
# x: Bytes
str(x, "ascii")
_ = bs "abc"