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 erg_common::{RcArray, Str};
use OpKind::*; use OpKind::*;
use erg_parser::ast::Dict as AstDict;
use erg_parser::ast::Set as AstSet;
use erg_parser::ast::*; use erg_parser::ast::*;
use erg_parser::token::{Token, TokenKind}; use erg_parser::token::{Token, TokenKind};
@ -227,10 +229,21 @@ impl Context {
let args = self.eval_args(&call.args)?; let args = self.eval_args(&call.args)?;
self.call(subr, args, call.loc()) self.call(subr, args, call.loc())
} }
Accessor::Attr(_attr) => todo!(), // TODO: eval attr
Accessor::TupleAttr(_attr) => todo!(), Accessor::Attr(_attr) => Err(EvalErrors::from(EvalError::not_const_expr(
Accessor::Subscr(_subscr) => todo!(), self.cfg.input.clone(),
Accessor::TypeApp(_type_app) => todo!(), 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 { } else {
todo!() todo!()
@ -306,6 +319,52 @@ impl Context {
Ok(ValueObj::Array(RcArray::from(elems))) 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> { fn eval_const_record(&self, record: &Record) -> EvalResult<ValueObj> {
match record { match record {
Record::Normal(rec) => self.eval_const_normal_record(rec), Record::Normal(rec) => self.eval_const_normal_record(rec),
@ -456,9 +515,17 @@ impl Context {
Expr::Call(call) => self.eval_const_call(call), Expr::Call(call) => self.eval_const_call(call),
Expr::Def(def) => self.eval_const_def(def), Expr::Def(def) => self.eval_const_def(def),
Expr::Array(arr) => self.eval_const_array(arr), 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::Record(rec) => self.eval_const_record(rec),
Expr::Lambda(lambda) => self.eval_const_lambda(lambda), 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,11 +351,24 @@ 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 Some(parent) = self.get_outer().or_else(|| self.get_builtins()) {
if let Ok(vi) = parent.rec_get_var_info(ident, acc_kind, input, namespace) { match parent.rec_get_var_info(ident, acc_kind, input, namespace) {
Ok(vi) Ok(vi) => Ok(vi),
} else { Err(err) if err.core.kind == ErrorKind::DummyError => {
Err(TyCheckError::no_var_error( Err(TyCheckError::no_var_error(
input.clone(), input.clone(),
line!() as usize, line!() as usize,
@ -365,6 +378,8 @@ impl Context {
self.get_similar_name(ident.inspect()), self.get_similar_name(ident.inspect()),
)) ))
} }
Err(err) => Err(err),
}
} else { } else {
Err(TyCheckError::dummy( Err(TyCheckError::dummy(
self.cfg.input.clone(), self.cfg.input.clone(),

View file

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

View file

@ -37,6 +37,8 @@ use Visibility::*;
use super::instantiate::TyVarCache; use super::instantiate::TyVarCache;
const UBAR: &Str = &Str::ever("_");
impl Context { impl Context {
/// If it is a constant that is defined, there must be no variable of the same name defined across all scopes /// 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( pub(crate) fn registered_info(
@ -62,15 +64,20 @@ impl Context {
} }
} }
fn _declare_var( fn pre_define_var(
&mut self, &mut self,
sig: &ast::VarSignature, sig: &ast::VarSignature,
opt_t: Option<Type>, opt_t: Option<Type>,
id: Option<DefId>, id: Option<DefId>,
) -> TyCheckResult<()> { ) -> TyCheckResult<()> {
let muty = Mutability::from(&sig.inspect().unwrap()[..]); let muty = Mutability::from(&sig.inspect().unwrap_or(UBAR)[..]);
match &sig.pat { let ident = match &sig.pat {
ast::VarPattern::Ident(ident) => { ast::VarPattern::Ident(ident) => ident,
ast::VarPattern::Discard(_) => {
return Ok(());
}
_ => todo!(),
};
let vis = ident.vis(); let vis = ident.vis();
let kind = id.map_or(VarKind::Declared, VarKind::Defined); 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)?; let sig_t = self.instantiate_var_sig_t(sig.t_spec.as_ref(), opt_t, PreRegister)?;
@ -83,16 +90,13 @@ impl Context {
ident.name.inspect(), ident.name.inspect(),
))) )))
} else { } else {
self.decls.insert( self.future_defined_locals.insert(
ident.name.clone(), ident.name.clone(),
VarInfo::new(sig_t, muty, vis, kind, None, self.impl_of(), None), VarInfo::new(sig_t, muty, vis, kind, None, self.impl_of(), None),
); );
Ok(()) Ok(())
} }
} }
_ => todo!(),
}
}
pub(crate) fn declare_sub( pub(crate) fn declare_sub(
&mut self, &mut self,
@ -555,8 +559,7 @@ impl Context {
pub(crate) fn preregister_def(&mut self, def: &ast::Def) -> TyCheckResult<()> { pub(crate) fn preregister_def(&mut self, def: &ast::Def) -> TyCheckResult<()> {
let id = Some(def.body.id); 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 { match &def.sig {
ast::Signature::Subr(sig) => { ast::Signature::Subr(sig) => {
if sig.is_const() { if sig.is_const() {
@ -587,7 +590,8 @@ impl Context {
self.declare_sub(sig, id)?; self.declare_sub(sig, id)?;
} }
} }
ast::Signature::Var(sig) if sig.is_const() => { ast::Signature::Var(sig) => {
if sig.is_const() {
let kind = ContextKind::from(def.def_kind()); let kind = ContextKind::from(def.def_kind());
self.grow(__name__, kind, sig.vis(), None); self.grow(__name__, kind, sig.vis(), None);
let (obj, const_t) = match self.eval_const_block(&def.body.block) { let (obj, const_t) = match self.eval_const_block(&def.body.block) {
@ -611,8 +615,14 @@ impl Context {
if let Some(ident) = sig.ident() { if let Some(ident) = sig.ident() {
self.register_gen_const(ident, obj)?; 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(()) 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( pub fn no_type_error(
input: Input, input: Input,
errno: usize, 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 erg_common::{RcArray, Str};
use super::codeobj::CodeObj; 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::free::fresh_varname;
use super::typaram::TyParam; use super::typaram::TyParam;
use super::{ConstSubr, HasType, Predicate, Type}; use super::{ConstSubr, HasType, Predicate, Type};
@ -743,7 +743,12 @@ impl ValueObj {
arr.iter().next().unwrap().class(), arr.iter().next().unwrap().class(),
TyParam::value(arr.len()), 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::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::Set(st) => set_t(st.iter().next().unwrap().class(), TyParam::value(st.len())),
Self::Code(_) => Type::Code, Self::Code(_) => Type::Code,
@ -771,7 +776,13 @@ impl ValueObj {
Self::Array(arr) => poly( Self::Array(arr) => poly(
"Array!", "Array!",
vec![ 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(), TyParam::value(arr.len()).mutate(),
], ],
), ),