Add procedure assignment check

This commit is contained in:
Shunsuke Shibayama 2022-10-03 23:09:07 +09:00
parent 2f33c9b15d
commit 6d903d2575
7 changed files with 81 additions and 29 deletions

View file

@ -633,10 +633,12 @@ impl Context {
))
} else {
match obj {
ValueObj::Type(t) => {
let gen = enum_unwrap!(t, TypeObj::Generated);
self.register_gen_type(ident, gen);
}
ValueObj::Type(t) => match t {
TypeObj::Generated(gen) => {
self.register_gen_type(ident, gen);
}
TypeObj::Builtin(_t) => panic!("aliasing bug"),
},
// TODO: not all value objects are comparable
other => {
let id = DefId(get_hash(ident));

View file

@ -9,8 +9,10 @@ use erg_common::vis::Visibility;
use erg_common::Str;
use Visibility::*;
use erg_type::HasType;
use crate::error::{EffectError, EffectErrors};
use crate::hir::{Accessor, Array, Def, Expr, Signature, Tuple, HIR};
use crate::hir::{Array, Def, Expr, Signature, Tuple, HIR};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum BlockKind {
@ -168,6 +170,18 @@ impl SideEffectChecker {
}
fn check_def(&mut self, def: &Def) {
if !def.sig.is_subr() {
let expr = def.body.block.last().unwrap();
if !def.sig.is_procedural() && expr.t().is_procedural() {
self.errs.push(EffectError::proc_assign_error(
self.cfg.input.clone(),
line!() as usize,
&def.sig,
self.full_path(),
));
}
return;
}
let name_and_vis = match &def.sig {
Signature::Var(var) => (var.inspect().clone(), var.vis()),
Signature::Subr(subr) => (subr.ident.inspect().clone(), subr.ident.vis()),
@ -268,7 +282,7 @@ impl SideEffectChecker {
}
// 引数がproceduralでも関数呼び出しなら副作用なし
Expr::Call(call) => {
if (self.is_procedural(&call.obj)
if (call.obj.t().is_procedural()
|| call
.method_name
.as_ref()
@ -318,19 +332,4 @@ impl SideEffectChecker {
_ => {}
}
}
fn is_procedural(&self, expr: &Expr) -> bool {
match expr {
Expr::Lambda(lambda) => lambda.is_procedural(),
// 引数がproceduralでも関数呼び出しなら副作用なし
Expr::Call(call) => self.is_procedural(&call.obj),
Expr::Accessor(Accessor::Ident(ident)) => ident.name.is_procedural(),
// procedural: x.y! (e.g. Array.sample!)
// !procedural: !x.y
Expr::Accessor(Accessor::Attr(attr)) => attr.ident.is_procedural(),
Expr::Accessor(_) => todo!(),
Expr::TypeAsc(tasc) => self.is_procedural(&tasc.expr),
_ => false,
}
}
}

View file

@ -16,7 +16,7 @@ use erg_parser::error::{ParserRunnerError, ParserRunnerErrors};
use erg_type::{Predicate, Type};
use crate::hir::{Expr, Identifier};
use crate::hir::{Expr, Identifier, Signature};
/// dname is for "double under name"
pub fn binop_to_dname(op: &str) -> &str {
@ -992,6 +992,38 @@ impl EffectError {
caused_by.into(),
)
}
pub fn proc_assign_error<S: Into<AtomicStr>>(
input: Input,
errno: usize,
sig: &Signature,
caused_by: S,
) -> Self {
Self::new(
ErrorCore::new(
errno,
HasEffect,
sig.loc(),
switch_lang!(
"japanese" => "プロシージャを通常の変数に代入することはできません",
"simplified_chinese" => "不能将过程赋值给普通变量",
"traditional_chinese" => "不能將過程賦值給普通變量",
"english" => "cannot assign a procedure to a normal variable",
),
Some(
switch_lang!(
"japanese" => "変数の末尾に`!`をつけてください",
"simplified_chinese" => "请在变量名后加上`!`",
"traditional_chinese" => "請在變量名後加上`!`",
"english" => "add `!` to the end of the variable name",
)
.into(),
),
),
input,
caused_by.into(),
)
}
}
pub type OwnershipError = TyCheckError;

View file

@ -252,7 +252,7 @@ impl ASTLowerer {
let maybe_len = self.ctx.eval_const_expr(len, None);
match maybe_len {
Ok(v @ ValueObj::Nat(_)) => {
if elem.ref_t().is_mut() {
if elem.ref_t().is_mut_type() {
builtin_poly(
"ArrayWithMutType!",
vec![TyParam::t(elem.t()), TyParam::Value(v)],
@ -262,7 +262,7 @@ impl ASTLowerer {
}
}
Ok(v @ ValueObj::Mut(_)) if v.class() == builtin_mono("Nat!") => {
if elem.ref_t().is_mut() {
if elem.ref_t().is_mut_type() {
builtin_poly(
"ArrayWithMutTypeAndLength!",
vec![TyParam::t(elem.t()), TyParam::Value(v)],
@ -274,7 +274,7 @@ impl ASTLowerer {
Ok(other) => todo!("{other} is not a Nat object"),
// REVIEW: is it ok to ignore the error?
Err(_e) => {
if elem.ref_t().is_mut() {
if elem.ref_t().is_mut_type() {
builtin_poly(
"ArrayWithMutType!",
vec![TyParam::t(elem.t()), TyParam::erased(Type::Nat)],

View file

@ -204,7 +204,7 @@ impl OwnershipChecker {
self.errs.push(e);
return;
}
if acc.ref_t().is_mut() && ownership.is_owned() && !chunk {
if acc.ref_t().is_mut_type() && ownership.is_owned() && !chunk {
self.drop(ident);
}
}

View file

@ -1684,11 +1684,25 @@ impl Type {
}
}
pub fn is_mut(&self) -> bool {
/// Procedure or MutType?
pub fn is_procedural(&self) -> bool {
match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_procedural(),
Self::Callable { .. } => true,
Self::Subr(subr) if subr.kind == SubrKind::Proc => true,
Self::Refinement(refine) =>
refine.t.is_procedural() || refine.preds.iter().any(|pred|
matches!(pred, Predicate::Equal{ rhs, .. } if pred.mentions(&refine.var) && rhs.name().map(|n| n.ends_with('!')).unwrap_or(false))
),
_ => false,
}
}
pub fn is_mut_type(&self) -> bool {
match self {
Self::FreeVar(fv) => {
if fv.is_linked() {
fv.crack().is_mut()
fv.crack().is_mut_type()
} else {
fv.unbound_name().unwrap().ends_with('!')
}
@ -1700,7 +1714,7 @@ impl Type {
| Self::BuiltinPoly { name, .. }
| Self::PolyQVar { name, .. }
| Self::MonoProj { rhs: name, .. } => name.ends_with('!'),
Self::Refinement(refine) => refine.t.is_mut(),
Self::Refinement(refine) => refine.t.is_mut_type(),
_ => false,
}
}

View file

@ -7,3 +7,8 @@ if True, do:
f x: Int = log x
g x: Int = print! x # this should cause an effect error
echo = print! # this should be an effect error
_echo = # this is OK
print! 1
log