Skip to content

Commit

Permalink
Revert "revert: fix: infinite recursion bug"
Browse files Browse the repository at this point in the history
This reverts commit e6bbf9e.
  • Loading branch information
mtshiba committed Sep 14, 2024
1 parent e6bbf9e commit a710a59
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 7 deletions.
100 changes: 96 additions & 4 deletions crates/erg_compiler/context/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<TyParam> {
// 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)
Expand Down Expand Up @@ -1799,7 +1805,9 @@ impl Context {
}
}

/// val: may be unevaluated
pub(crate) fn eval_unary_tp(&self, op: OpKind, val: TyParam) -> EvalResult<TyParam> {
// 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() => {
Expand All @@ -1814,7 +1822,13 @@ impl Context {
}
}

/// args: may be unevaluated
pub(crate) fn eval_app(&self, name: Str, args: Vec<TyParam>) -> Failable<TyParam> {
/*
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()))
Expand All @@ -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<TyParam> {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<Result<ValueObj, TyParam>> {
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<TyParam>,
t_loc: &impl Locational,
) -> EvalResult<Result<ValueObj, TyParam>> {
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]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
}
}
Expand Down Expand Up @@ -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),
}
}
Expand Down Expand Up @@ -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! {};
Expand Down
4 changes: 4 additions & 0 deletions crates/erg_compiler/context/initialize/const_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();
// 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));
Expand Down
2 changes: 1 addition & 1 deletion crates/erg_compiler/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3579,7 +3579,7 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
// 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() {
Expand Down
81 changes: 79 additions & 2 deletions crates/erg_compiler/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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),
Expand Down
28 changes: 28 additions & 0 deletions crates/erg_compiler/ty/typaram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions crates/erg_compiler/ty/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit a710a59

Please sign in to comment.