mirror of
https://github.com/erg-lang/erg.git
synced 2025-08-04 02:39:20 +00:00
fix: infinite recursion bug
This commit is contained in:
parent
9f789199b9
commit
82bc710827
6 changed files with 223 additions and 7 deletions
|
@ -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<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)
|
||||
|
@ -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() => {
|
||||
|
@ -1814,7 +1822,11 @@ 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()))
|
||||
|
@ -1833,6 +1845,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> {
|
||||
|
@ -2060,6 +2074,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 +2219,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 +2400,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 +2594,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]
|
||||
|
@ -2660,6 +2705,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 +2753,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 +2842,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 +2894,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! {};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue