From a710a59ff781036a54334a7700ac405ab1e7d920 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama Date: Sun, 15 Sep 2024 00:31:06 +0900 Subject: [PATCH] Revert "revert: fix: infinite recursion bug" This reverts commit e6bbf9ea848eb3a702b593182743e51f6582dd2e. --- crates/erg_compiler/context/eval.rs | 100 +++++++++++++++++- .../context/initialize/const_func.rs | 4 + crates/erg_compiler/lower.rs | 2 +- crates/erg_compiler/ty/mod.rs | 81 +++++++++++++- crates/erg_compiler/ty/typaram.rs | 28 +++++ crates/erg_compiler/ty/value.rs | 17 +++ 6 files changed, 225 insertions(+), 7 deletions(-) diff --git a/crates/erg_compiler/context/eval.rs b/crates/erg_compiler/context/eval.rs index 6fc5a7d01..1f89509f0 100644 --- a/crates/erg_compiler/context/eval.rs +++ b/crates/erg_compiler/context/eval.rs @@ -766,6 +766,9 @@ impl Context { } } + /// Assume that `args` has already been evaluated. + /// Projection types may remain, but how they are handled varies by each const function. + /// For example, `list_union` returns `Type::Obj` if it contains a projection type. pub(crate) fn call( &self, subr: ConstSubr, @@ -1677,12 +1680,15 @@ impl Context { } } + /// lhs, rhs: may be unevaluated pub(crate) fn eval_bin_tp( &self, op: OpKind, lhs: TyParam, rhs: TyParam, ) -> EvalResult { + // let lhs = self.eval_tp(lhs).map_err(|(_, es)| es)?; + // let rhs = self.eval_tp(rhs).map_err(|(_, es)| es)?; match (lhs, rhs) { (TyParam::Value(lhs), TyParam::Value(rhs)) => { self.eval_bin(op, lhs, rhs).map(TyParam::value) @@ -1799,7 +1805,9 @@ impl Context { } } + /// val: may be unevaluated pub(crate) fn eval_unary_tp(&self, op: OpKind, val: TyParam) -> EvalResult { + // let val = self.eval_tp(val).map_err(|(_, es)| es)?; match val { TyParam::Value(c) => self.eval_unary_val(op, c).map(TyParam::Value), TyParam::FreeVar(fv) if fv.is_linked() => { @@ -1814,7 +1822,13 @@ impl Context { } } + /// args: may be unevaluated pub(crate) fn eval_app(&self, name: Str, args: Vec) -> Failable { + /* + let args = self + .eval_type_args(args) + .map_err(|(args, es)| (TyParam::app(name.clone(), args), es))?; + */ if let Ok(value_args) = args .iter() .map(|tp| self.convert_tp_into_value(tp.clone())) @@ -1833,6 +1847,8 @@ impl Context { } } + /// Evaluate type parameters. + /// Some type parameters may be withheld from evaluation because they can be evaluated but only result in a `Failure` as is. /// Quantified variables, etc. are returned as is. /// 量化変数などはそのまま返す pub(crate) fn eval_tp(&self, p: TyParam) -> Failable { @@ -2060,6 +2076,7 @@ impl Context { } /// Evaluate `substituted`. + /// Some types may be withheld from evaluation because they can be evaluated but are only `Failure/Never` as is (e.g. `?T(:> Never).Proj`). /// If the evaluation fails, return a harmless type (filled with `Failure`) and errors pub(crate) fn eval_t_params( &self, @@ -2204,9 +2221,6 @@ impl Context { Ok(Type::Refinement(refine)) } } - // [?T; 0].MutType! == [?T; !0] - // ?T(<: Add(?R(:> Int))).Output == ?T(<: Add(?R)).Output - // ?T(:> Int, <: Add(?R(:> Int))).Output == Int Type::Proj { lhs, rhs } => self .eval_proj(*lhs, rhs, level, t_loc) .map_err(|errs| (Failure, errs)), @@ -2388,6 +2402,11 @@ impl Context { /// This may do nothing (be careful with recursive calls). /// lhs: mainly class, may be unevaluated + /// ```erg + /// [?T; 0].MutType! == ![?T; 0] + /// ?T(<: Add(?R(:> Int))).Output == ?T(<: Add(?R)).Output + /// ?T(:> Int, <: Add(?R(:> Int))).Output == Int + /// ``` pub(crate) fn eval_proj( &self, lhs: Type, @@ -2577,6 +2596,34 @@ impl Context { Ok(lhs.proj(rhs)) } + /// lhs: may be unevaluated + pub(crate) fn eval_value_proj( + &self, + lhs: TyParam, + rhs: Str, + t_loc: &impl Locational, + ) -> EvalResult> { + match self.eval_tp_proj(lhs, rhs, t_loc) { + Ok(TyParam::Value(val)) => Ok(Ok(val)), + Ok(tp) => Ok(Err(tp)), + Err(errs) => Err(errs), + } + } + + pub fn eval_value_proj_call( + &self, + lhs: TyParam, + attr_name: Str, + args: Vec, + t_loc: &impl Locational, + ) -> EvalResult> { + match self.eval_proj_call(lhs, attr_name, args, t_loc) { + Ok(TyParam::Value(val)) => Ok(Ok(val)), + Ok(tp) => Ok(Err(tp)), + Err(errs) => Err(errs), + } + } + /// ```erg /// TyParam::Type(Int) => Int /// [{1}, {2}, {3}] => [{1, 2, 3}; 3] @@ -2660,6 +2707,26 @@ impl Context { Ok(Type::Poly { name, params: args }) } } + TyParam::BinOp { op, lhs, rhs } => match op { + OpKind::And => { + let lhs = self.convert_tp_into_type(*lhs)?; + let rhs = self.convert_tp_into_type(*rhs)?; + Ok(self.intersection(&lhs, &rhs)) + } + OpKind::Or => { + let lhs = self.convert_tp_into_type(*lhs)?; + let rhs = self.convert_tp_into_type(*rhs)?; + Ok(self.union(&lhs, &rhs)) + } + _ => Err(TyParam::BinOp { op, lhs, rhs }), + }, + TyParam::UnaryOp { op, val } => match op { + OpKind::Not => { + let val = self.convert_tp_into_type(*val)?; + Ok(self.complement(&val)) + } + _ => Err(TyParam::UnaryOp { op, val }), + }, TyParam::Proj { obj, attr } => { let lhs = self.convert_tp_into_type(*obj.clone())?; let Some(ty_ctx) = self.get_nominal_type_ctx(&lhs) else { @@ -2688,7 +2755,7 @@ impl Context { // TyParam::Erased(_t) => Ok(Type::Obj), TyParam::Value(v) => self.convert_value_into_type(v).map_err(TyParam::Value), TyParam::Erased(t) if t.is_type() => Ok(Type::Obj), - // TODO: DataClass, ... + TyParam::Failure => Ok(Type::Failure), other => Err(other), } } @@ -2777,6 +2844,29 @@ impl Context { name, params, block, sig_t, )))) } + TyParam::DataClass { name, fields } => { + let mut new = dict! {}; + for (name, elem) in fields { + let elem = self.convert_tp_into_value(elem)?; + new.insert(name, elem); + } + Ok(ValueObj::DataClass { name, fields: new }) + } + /*TyParam::Proj { obj, attr } => { + match self.eval_value_proj(*obj.clone(), attr.clone(), &()) { + Ok(Ok(value)) => Ok(value), + Ok(Err(tp)) => self.convert_tp_into_type(tp).map(ValueObj::builtin_type), + Err(_) => Err(obj.proj(attr)), + } + } + TyParam::ProjCall { obj, attr, args } => { + match self.eval_value_proj_call(*obj.clone(), attr.clone(), args.clone(), &()) { + Ok(Ok(value)) => Ok(value), + Ok(Err(tp)) => self.convert_tp_into_type(tp).map(ValueObj::builtin_type), + Err(_) => Err(obj.proj(attr)), + } + } + TyParam::Type(t) => Ok(ValueObj::builtin_type(*t)),*/ other => self.convert_tp_into_type(other).map(ValueObj::builtin_type), } } @@ -2806,6 +2896,8 @@ impl Context { ValueObj::Failure => Ok(Type::Failure), ValueObj::Ellipsis => Ok(Type::Ellipsis), ValueObj::NotImplemented => Ok(Type::NotImplementedType), + ValueObj::Inf => Ok(Type::Inf), + ValueObj::NegInf => Ok(Type::NegInf), ValueObj::Type(t) => Ok(t.into_typ()), ValueObj::Record(rec) => { let mut fields = dict! {}; diff --git a/crates/erg_compiler/context/initialize/const_func.rs b/crates/erg_compiler/context/initialize/const_func.rs index e98178c47..e5a556d6b 100644 --- a/crates/erg_compiler/context/initialize/const_func.rs +++ b/crates/erg_compiler/context/initialize/const_func.rs @@ -492,6 +492,10 @@ pub(crate) fn list_union(mut args: ValueArgs, ctx: &Context) -> EvalValueResult< .iter() .flat_map(|t| ctx.convert_value_into_type(t.clone())) .collect::>(); + // args must already be evaluated + if slf.iter().any(|t| t.has_proj() || t.has_proj_call()) { + return Ok(TyParam::t(Type::Obj)); + } let union = slf .iter() .fold(Type::Never, |union, t| ctx.union(&union, t)); diff --git a/crates/erg_compiler/lower.rs b/crates/erg_compiler/lower.rs index 4d2d7160a..04f45d4c7 100644 --- a/crates/erg_compiler/lower.rs +++ b/crates/erg_compiler/lower.rs @@ -3579,7 +3579,7 @@ impl GenericASTLowerer { // e.g. casted == {x: Obj | x != None}, expr: Int or NoneType => intersec == Int let intersec = self.module.context.intersection(expr.ref_t(), &casted); // bad narrowing: C and Structural { foo = Foo } - if expr.ref_t().is_projection() + if expr.ref_t().is_proj() || (intersec != Type::Never && intersec.ands().iter().all(|t| !t.is_structural())) { if let Some(ref_mut_t) = expr.ref_mut_t() { diff --git a/crates/erg_compiler/ty/mod.rs b/crates/erg_compiler/ty/mod.rs index f11b66679..7f545ed14 100644 --- a/crates/erg_compiler/ty/mod.rs +++ b/crates/erg_compiler/ty/mod.rs @@ -2522,14 +2522,30 @@ impl Type { } } - pub fn is_projection(&self) -> bool { + pub fn is_proj(&self) -> bool { match self { - Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_projection(), + Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_proj(), Self::Proj { .. } | Self::ProjCall { .. } => true, _ => false, } } + pub fn has_proj(&self) -> bool { + self.is_proj() || self.has_type_satisfies(|t| t.is_proj()) + } + + pub fn is_proj_call(&self) -> bool { + match self { + Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_proj_call(), + Self::ProjCall { .. } => true, + _ => false, + } + } + + pub fn has_proj_call(&self) -> bool { + self.is_proj_call() || self.has_type_satisfies(|t| t.is_proj_call()) + } + pub fn is_intersection_type(&self) -> bool { match self { Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_intersection_type(), @@ -2903,6 +2919,67 @@ impl Type { } } + pub fn has_type_satisfies(&self, f: impl Fn(&Type) -> bool + Copy) -> bool { + match self { + Self::FreeVar(fv) if fv.is_linked() => fv.crack().has_type_satisfies(f), + Self::FreeVar(fv) if fv.constraint_is_typeof() => { + fv.get_type().unwrap().has_type_satisfies(f) + } + Self::FreeVar(fv) => fv + .get_subsup() + .map(|(sub, sup)| { + fv.do_avoiding_recursion(|| { + sub.has_type_satisfies(f) || sup.has_type_satisfies(f) + }) + }) + .unwrap_or(false), + Self::Record(rec) => rec.iter().any(|(_, t)| t.has_type_satisfies(f)), + Self::NamedTuple(rec) => rec.iter().any(|(_, t)| t.has_type_satisfies(f)), + Self::Poly { params, .. } => params.iter().any(|tp| tp.has_type_satisfies(f)), + Self::Quantified(t) => t.has_type_satisfies(f), + Self::Subr(subr) => { + subr.non_default_params + .iter() + .any(|pt| pt.typ().has_type_satisfies(f)) + || subr + .var_params + .as_ref() + .map_or(false, |pt| pt.typ().has_type_satisfies(f)) + || subr + .default_params + .iter() + .any(|pt| pt.typ().has_type_satisfies(f)) + || subr + .default_params + .iter() + .any(|pt| pt.default_typ().map_or(false, |t| t.has_type_satisfies(f))) + || subr.return_t.has_type_satisfies(f) + } + // TODO: preds + Self::Refinement(refine) => refine.t.has_type_satisfies(f), + Self::Structural(ty) => ty.has_type_satisfies(f), + Self::Proj { lhs, .. } => lhs.has_type_satisfies(f), + Self::ProjCall { lhs, args, .. } => { + lhs.has_type_satisfies(f) || args.iter().any(|t| t.has_type_satisfies(f)) + } + Self::And(lhs, rhs) | Self::Or(lhs, rhs) => { + lhs.has_type_satisfies(f) || rhs.has_type_satisfies(f) + } + Self::Not(t) => t.has_type_satisfies(f), + Self::Ref(t) => t.has_type_satisfies(f), + Self::RefMut { before, after } => { + before.has_type_satisfies(f) + || after.as_ref().map_or(false, |t| t.has_type_satisfies(f)) + } + Self::Bounded { sub, sup } => sub.has_type_satisfies(f) || sup.has_type_satisfies(f), + Self::Callable { param_ts, return_t } => { + param_ts.iter().any(|t| t.has_type_satisfies(f)) || return_t.has_type_satisfies(f) + } + Self::Guard(guard) => guard.to.has_type_satisfies(f), + mono_type_pattern!() => false, + } + } + pub fn contains_tvar_in_constraint(&self, target: &FreeTyVar) -> bool { match self { Self::FreeVar(fv) if fv.is_linked() => fv.crack().contains_tvar_in_constraint(target), diff --git a/crates/erg_compiler/ty/typaram.rs b/crates/erg_compiler/ty/typaram.rs index be6a528c5..d104c298b 100644 --- a/crates/erg_compiler/ty/typaram.rs +++ b/crates/erg_compiler/ty/typaram.rs @@ -1265,6 +1265,34 @@ impl TyParam { } } + pub fn has_type_satisfies(&self, f: impl Fn(&Type) -> bool + Copy) -> bool { + match self { + Self::FreeVar(fv) if fv.is_linked() => fv.crack().has_type_satisfies(f), + Self::FreeVar(fv) => fv.get_type().map_or(false, |t| t.has_type_satisfies(f)), + Self::Type(t) => t.has_type_satisfies(f), + Self::Erased(t) => t.has_type_satisfies(f), + Self::Proj { obj, .. } => obj.has_type_satisfies(f), + Self::ProjCall { obj, args, .. } => { + obj.has_type_satisfies(f) || args.iter().any(|t| t.has_type_satisfies(f)) + } + Self::List(ts) | Self::Tuple(ts) => ts.iter().any(|t| t.has_type_satisfies(f)), + Self::UnsizedList(elem) => elem.has_type_satisfies(f), + Self::Set(ts) => ts.iter().any(|t| t.has_type_satisfies(f)), + Self::Dict(ts) => ts + .iter() + .any(|(k, v)| k.has_type_satisfies(f) || v.has_type_satisfies(f)), + Self::Record(rec) | Self::DataClass { fields: rec, .. } => { + rec.iter().any(|(_, tp)| tp.has_type_satisfies(f)) + } + Self::Lambda(lambda) => lambda.body.iter().any(|tp| tp.has_type_satisfies(f)), + Self::UnaryOp { val, .. } => val.has_type_satisfies(f), + Self::BinOp { lhs, rhs, .. } => lhs.has_type_satisfies(f) || rhs.has_type_satisfies(f), + Self::App { args, .. } => args.iter().any(|p| p.has_type_satisfies(f)), + Self::Value(val) => val.has_type_satisfies(f), + Self::Mono(_) | Self::Failure => false, + } + } + pub fn contains_tp(&self, target: &TyParam) -> bool { if self == target { return true; diff --git a/crates/erg_compiler/ty/value.rs b/crates/erg_compiler/ty/value.rs index e95d55d90..878495016 100644 --- a/crates/erg_compiler/ty/value.rs +++ b/crates/erg_compiler/ty/value.rs @@ -2078,6 +2078,23 @@ impl ValueObj { } } + pub fn has_type_satisfies(&self, f: impl Fn(&Type) -> bool + Copy) -> bool { + match self { + Self::Type(t) => t.typ().has_type_satisfies(f), + Self::List(ts) | Self::Tuple(ts) => ts.iter().any(|t| t.has_type_satisfies(f)), + Self::UnsizedList(elem) => elem.has_type_satisfies(f), + Self::Set(ts) => ts.iter().any(|t| t.has_type_satisfies(f)), + Self::Dict(ts) => ts + .iter() + .any(|(k, v)| k.has_type_satisfies(f) || v.has_type_satisfies(f)), + Self::Record(rec) | Self::DataClass { fields: rec, .. } => { + rec.iter().any(|(_, tp)| tp.has_type_satisfies(f)) + } + Self::Subr(_) => false, + mono_value_pattern!() => false, + } + } + pub fn has_unbound_var(&self) -> bool { match self { Self::Type(t) => t.typ().has_unbound_var(),