Improve error message for referring to a variable before its definition

This commit is contained in:
Shunsuke Shibayama 2022-11-28 10:38:01 +09:00
parent 80c479ecbd
commit fde5a33d54
6 changed files with 210 additions and 66 deletions

View file

@ -13,6 +13,8 @@ use erg_common::{dict, fn_name, option_enum_unwrap, set};
use erg_common::{RcArray, Str};
use OpKind::*;
use erg_parser::ast::Dict as AstDict;
use erg_parser::ast::Set as AstSet;
use erg_parser::ast::*;
use erg_parser::token::{Token, TokenKind};
@ -227,10 +229,21 @@ impl Context {
let args = self.eval_args(&call.args)?;
self.call(subr, args, call.loc())
}
Accessor::Attr(_attr) => todo!(),
Accessor::TupleAttr(_attr) => todo!(),
Accessor::Subscr(_subscr) => todo!(),
Accessor::TypeApp(_type_app) => todo!(),
// TODO: eval attr
Accessor::Attr(_attr) => Err(EvalErrors::from(EvalError::not_const_expr(
self.cfg.input.clone(),
line!() as usize,
call.loc(),
self.caused_by(),
))),
// TODO: eval type app
Accessor::TypeApp(_type_app) => Err(EvalErrors::from(EvalError::not_const_expr(
self.cfg.input.clone(),
line!() as usize,
call.loc(),
self.caused_by(),
))),
_ => unreachable!(),
}
} else {
todo!()
@ -306,6 +319,52 @@ impl Context {
Ok(ValueObj::Array(RcArray::from(elems)))
}
fn eval_const_set(&self, set: &AstSet) -> EvalResult<ValueObj> {
let mut elems = vec![];
match set {
AstSet::Normal(arr) => {
for elem in arr.elems.pos_args().iter() {
let elem = self.eval_const_expr(&elem.expr)?;
elems.push(elem);
}
}
_ => {
todo!()
}
}
Ok(ValueObj::Set(Set::from(elems)))
}
fn eval_const_dict(&self, dict: &AstDict) -> EvalResult<ValueObj> {
let mut elems = dict! {};
match dict {
AstDict::Normal(dic) => {
for elem in dic.kvs.iter() {
let key = self.eval_const_expr(&elem.key)?;
let value = self.eval_const_expr(&elem.value)?;
elems.insert(key, value);
}
}
_ => {
todo!()
}
}
Ok(ValueObj::Dict(elems))
}
fn eval_const_tuple(&self, tuple: &Tuple) -> EvalResult<ValueObj> {
let mut elems = vec![];
match tuple {
Tuple::Normal(arr) => {
for elem in arr.elems.pos_args().iter() {
let elem = self.eval_const_expr(&elem.expr)?;
elems.push(elem);
}
}
}
Ok(ValueObj::Tuple(RcArray::from(elems)))
}
fn eval_const_record(&self, record: &Record) -> EvalResult<ValueObj> {
match record {
Record::Normal(rec) => self.eval_const_normal_record(rec),
@ -456,9 +515,17 @@ impl Context {
Expr::Call(call) => self.eval_const_call(call),
Expr::Def(def) => self.eval_const_def(def),
Expr::Array(arr) => self.eval_const_array(arr),
Expr::Set(set) => self.eval_const_set(set),
Expr::Dict(dict) => self.eval_const_dict(dict),
Expr::Tuple(tuple) => self.eval_const_tuple(tuple),
Expr::Record(rec) => self.eval_const_record(rec),
Expr::Lambda(lambda) => self.eval_const_lambda(lambda),
other => todo!("{other}"),
other => Err(EvalErrors::from(EvalError::not_const_expr(
self.cfg.input.clone(),
line!() as usize,
other.loc(),
self.caused_by(),
))),
}
}

View file

@ -351,19 +351,34 @@ impl Context {
}
}
}
} else if let Some((name, _vi)) = self
.future_defined_locals
.get_key_value(&ident.inspect()[..])
{
return Err(TyCheckError::access_before_def_error(
input.clone(),
line!() as usize,
ident.loc(),
namespace.into(),
ident.inspect(),
name.ln_begin().unwrap(),
self.get_similar_name(ident.inspect()),
));
}
if let Some(parent) = self.get_outer().or_else(|| self.get_builtins()) {
if let Ok(vi) = parent.rec_get_var_info(ident, acc_kind, input, namespace) {
Ok(vi)
} else {
Err(TyCheckError::no_var_error(
input.clone(),
line!() as usize,
ident.loc(),
namespace.into(),
ident.inspect(),
self.get_similar_name(ident.inspect()),
))
match parent.rec_get_var_info(ident, acc_kind, input, namespace) {
Ok(vi) => Ok(vi),
Err(err) if err.core.kind == ErrorKind::DummyError => {
Err(TyCheckError::no_var_error(
input.clone(),
line!() as usize,
ident.loc(),
namespace.into(),
ident.inspect(),
self.get_similar_name(ident.inspect()),
))
}
Err(err) => Err(err),
}
} else {
Err(TyCheckError::dummy(

View file

@ -346,6 +346,8 @@ pub struct Context {
pub(crate) trait_impls: Dict<Str, Set<TypeRelationInstance>>,
/// stores declared names (not initialized)
pub(crate) decls: Dict<VarName, VarInfo>,
/// for error reporting
pub(crate) future_defined_locals: Dict<VarName, VarInfo>,
// stores defined names
// 型の一致はHashMapでは判定できないため、keyはVarNameとして1つずつ見ていく
/// ```python
@ -537,6 +539,7 @@ impl Context {
trait_impls: Dict::default(),
params: params_,
decls: Dict::default(),
future_defined_locals: Dict::default(),
locals: Dict::with_capacity(capacity),
consts: Dict::default(),
mono_types: Dict::default(),

View file

@ -37,6 +37,8 @@ use Visibility::*;
use super::instantiate::TyVarCache;
const UBAR: &Str = &Str::ever("_");
impl Context {
/// If it is a constant that is defined, there must be no variable of the same name defined across all scopes
pub(crate) fn registered_info(
@ -62,35 +64,37 @@ impl Context {
}
}
fn _declare_var(
fn pre_define_var(
&mut self,
sig: &ast::VarSignature,
opt_t: Option<Type>,
id: Option<DefId>,
) -> TyCheckResult<()> {
let muty = Mutability::from(&sig.inspect().unwrap()[..]);
match &sig.pat {
ast::VarPattern::Ident(ident) => {
let vis = ident.vis();
let kind = id.map_or(VarKind::Declared, VarKind::Defined);
let sig_t = self.instantiate_var_sig_t(sig.t_spec.as_ref(), opt_t, PreRegister)?;
if let Some(_decl) = self.decls.remove(&ident.name) {
Err(TyCheckErrors::from(TyCheckError::duplicate_decl_error(
self.cfg.input.clone(),
line!() as usize,
sig.loc(),
self.caused_by(),
ident.name.inspect(),
)))
} else {
self.decls.insert(
ident.name.clone(),
VarInfo::new(sig_t, muty, vis, kind, None, self.impl_of(), None),
);
Ok(())
}
let muty = Mutability::from(&sig.inspect().unwrap_or(UBAR)[..]);
let ident = match &sig.pat {
ast::VarPattern::Ident(ident) => ident,
ast::VarPattern::Discard(_) => {
return Ok(());
}
_ => todo!(),
};
let vis = ident.vis();
let kind = id.map_or(VarKind::Declared, VarKind::Defined);
let sig_t = self.instantiate_var_sig_t(sig.t_spec.as_ref(), opt_t, PreRegister)?;
if let Some(_decl) = self.decls.remove(&ident.name) {
Err(TyCheckErrors::from(TyCheckError::duplicate_decl_error(
self.cfg.input.clone(),
line!() as usize,
sig.loc(),
self.caused_by(),
ident.name.inspect(),
)))
} else {
self.future_defined_locals.insert(
ident.name.clone(),
VarInfo::new(sig_t, muty, vis, kind, None, self.impl_of(), None),
);
Ok(())
}
}
@ -555,8 +559,7 @@ impl Context {
pub(crate) fn preregister_def(&mut self, def: &ast::Def) -> TyCheckResult<()> {
let id = Some(def.body.id);
let ubar = Str::ever("_");
let __name__ = def.sig.ident().map(|i| i.inspect()).unwrap_or(&ubar);
let __name__ = def.sig.ident().map(|i| i.inspect()).unwrap_or(UBAR);
match &def.sig {
ast::Signature::Subr(sig) => {
if sig.is_const() {
@ -587,32 +590,39 @@ impl Context {
self.declare_sub(sig, id)?;
}
}
ast::Signature::Var(sig) if sig.is_const() => {
let kind = ContextKind::from(def.def_kind());
self.grow(__name__, kind, sig.vis(), None);
let (obj, const_t) = match self.eval_const_block(&def.body.block) {
Ok(obj) => (obj.clone(), v_enum(set! {obj})),
Err(e) => {
return Err(e);
ast::Signature::Var(sig) => {
if sig.is_const() {
let kind = ContextKind::from(def.def_kind());
self.grow(__name__, kind, sig.vis(), None);
let (obj, const_t) = match self.eval_const_block(&def.body.block) {
Ok(obj) => (obj.clone(), v_enum(set! {obj})),
Err(e) => {
return Err(e);
}
};
if let Some(spec) = sig.t_spec.as_ref() {
let mut dummy_tv_cache = TyVarCache::new(self.level, self);
let spec_t = self.instantiate_typespec(
spec,
None,
&mut dummy_tv_cache,
PreRegister,
false,
)?;
self.sub_unify(&const_t, &spec_t, def.body.loc(), None)?;
}
};
if let Some(spec) = sig.t_spec.as_ref() {
let mut dummy_tv_cache = TyVarCache::new(self.level, self);
let spec_t = self.instantiate_typespec(
spec,
None,
&mut dummy_tv_cache,
PreRegister,
false,
)?;
self.sub_unify(&const_t, &spec_t, def.body.loc(), None)?;
}
self.pop();
if let Some(ident) = sig.ident() {
self.register_gen_const(ident, obj)?;
self.pop();
if let Some(ident) = sig.ident() {
self.register_gen_const(ident, obj)?;
}
} else {
let opt_t = self
.eval_const_block(&def.body.block)
.map(|o| v_enum(set! {o}))
.ok();
self.pre_define_var(sig, opt_t, id)?;
}
}
_ => {}
}
Ok(())
}

View file

@ -1577,6 +1577,44 @@ impl LowerError {
)
}
pub fn access_before_def_error(
input: Input,
errno: usize,
loc: Location,
caused_by: String,
name: &str,
defined_line: usize,
similar_name: Option<&str>,
) -> Self {
let name = readable_name(name);
let hint = similar_name.map(|n| {
let n = StyledStr::new(n, Some(HINT), Some(ATTR));
switch_lang!(
"japanese" => format!("似た名前の変数があります: {n}"),
"simplified_chinese" => format!("存在相同名称变量: {n}"),
"traditional_chinese" => format!("存在相同名稱變量: {n}"),
"english" => format!("exists a similar name variable: {n}"),
)
});
let found = StyledString::new(name, Some(ERR), Some(ATTR));
Self::new(
ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], hint)],
switch_lang!(
"japanese" => format!("定義({defined_line}行目)より前で{found}を参照することは出来ません"),
"simplified_chinese" => format!("{found}定义({defined_line}行)之前引用是不允许的"),
"traditional_chinese" => format!("{found}定義({defined_line}行)之前引用是不允許的"),
"english" => format!("cannot reference {found} before its definition (line {defined_line})"),
),
errno,
NameError,
loc,
),
input,
caused_by,
)
}
pub fn no_type_error(
input: Input,
errno: usize,

View file

@ -20,7 +20,7 @@ use erg_common::{dict, fmt_iter, impl_display_from_debug, switch_lang};
use erg_common::{RcArray, Str};
use super::codeobj::CodeObj;
use super::constructors::{array_t, mono, poly, refinement, set_t, tuple_t};
use super::constructors::{array_t, dict_t, mono, poly, refinement, set_t, tuple_t};
use super::free::fresh_varname;
use super::typaram::TyParam;
use super::{ConstSubr, HasType, Predicate, Type};
@ -743,7 +743,12 @@ impl ValueObj {
arr.iter().next().unwrap().class(),
TyParam::value(arr.len()),
),
Self::Dict(_dict) => todo!(),
Self::Dict(dict) => {
let tp = dict
.iter()
.map(|(k, v)| (TyParam::value(k.clone()), TyParam::value(v.clone())));
dict_t(TyParam::Dict(tp.collect()))
}
Self::Tuple(tup) => tuple_t(tup.iter().map(|v| v.class()).collect()),
Self::Set(st) => set_t(st.iter().next().unwrap().class(), TyParam::value(st.len())),
Self::Code(_) => Type::Code,
@ -771,7 +776,13 @@ impl ValueObj {
Self::Array(arr) => poly(
"Array!",
vec![
TyParam::t(arr.iter().next().unwrap().class()),
// REVIEW: Never?
TyParam::t(
arr.iter()
.next()
.map(|elem| elem.class())
.unwrap_or(Type::Never),
),
TyParam::value(arr.len()).mutate(),
],
),