fix: distinguish between access to class/instance attr

This commit is contained in:
Shunsuke Shibayama 2023-06-08 22:17:52 +09:00
parent 1cbe2de706
commit d4b78eb020
10 changed files with 233 additions and 80 deletions

View file

@ -646,8 +646,8 @@ impl PyCodeGenerator {
}
StoreLoadKind::Local | StoreLoadKind::LocalConst => match acc_kind {
Name => LOAD_NAME as u8,
Attr => LOAD_ATTR as u8,
Method => LOAD_METHOD as u8,
UnboundAttr => LOAD_ATTR as u8,
BoundAttr => LOAD_METHOD as u8,
},
}
}
@ -669,9 +669,9 @@ impl PyCodeGenerator {
StoreLoadKind::Local | StoreLoadKind::LocalConst => {
match acc_kind {
Name => STORE_NAME as u8,
Attr => STORE_ATTR as u8,
UnboundAttr => STORE_ATTR as u8,
// cannot overwrite methods directly
Method => STORE_ATTR as u8,
BoundAttr => STORE_ATTR as u8,
}
}
}
@ -792,9 +792,9 @@ impl PyCodeGenerator {
log!(info "entered {} ({ident})", fn_name!());
let escaped = escape_ident(ident);
let name = self
.local_search(&escaped, Attr)
.local_search(&escaped, UnboundAttr)
.unwrap_or_else(|| self.register_attr(escaped));
let instr = self.select_load_instr(name.kind, Attr);
let instr = self.select_load_instr(name.kind, UnboundAttr);
self.write_instr(instr);
self.write_arg(name.idx);
if self.py_version.minor >= Some(11) {
@ -809,9 +809,9 @@ impl PyCodeGenerator {
}
let escaped = escape_ident(ident);
let name = self
.local_search(&escaped, Method)
.local_search(&escaped, BoundAttr)
.unwrap_or_else(|| self.register_method(escaped));
let instr = self.select_load_instr(name.kind, Method);
let instr = self.select_load_instr(name.kind, BoundAttr);
self.write_instr(instr);
self.write_arg(name.idx);
if self.py_version.minor >= Some(11) {
@ -866,7 +866,7 @@ impl PyCodeGenerator {
}
Accessor::Attr(attr) => {
self.emit_expr(*attr.obj);
self.emit_store_instr(attr.ident, Attr);
self.emit_store_instr(attr.ident, UnboundAttr);
}
}
}
@ -1010,7 +1010,7 @@ impl PyCodeGenerator {
self.emit_precall_and_call(argc);
} else {
match kind {
AccessKind::Method => self.write_instr(Opcode310::CALL_METHOD),
AccessKind::BoundAttr => self.write_instr(Opcode310::CALL_METHOD),
_ => self.write_instr(Opcode310::CALL_FUNCTION),
}
self.write_arg(argc);
@ -2188,7 +2188,7 @@ impl PyCodeGenerator {
let is_py_api = method_name.is_py_api();
self.emit_expr(obj);
self.emit_load_method_instr(method_name);
self.emit_args_311(args, Method, is_py_api);
self.emit_args_311(args, BoundAttr, is_py_api);
}
fn emit_var_args_311(&mut self, pos_len: usize, var_args: &PosArg) {
@ -3121,7 +3121,7 @@ impl PyCodeGenerator {
self.emit_load_name_instr(Identifier::private("#path"));
self.emit_load_method_instr(Identifier::public("append"));
self.emit_load_const(erg_std_path().to_str().unwrap());
self.emit_call_instr(1, Method);
self.emit_call_instr(1, BoundAttr);
self.stack_dec();
self.emit_pop_top();
let erg_std_mod = Identifier::public("_erg_std_prelude");

View file

@ -80,19 +80,31 @@ impl Name {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessKind {
Name,
Attr,
Method,
/// class/module attr
/// e.g. `Str.center`
UnboundAttr,
/// method/instance attr
/// e.g. `"aaa".center`
///
/// can also access class/module attrs
BoundAttr,
}
impl AccessKind {
pub const fn is_local(&self) -> bool {
matches!(self, Self::Name)
}
pub const fn is_attr(&self) -> bool {
matches!(self, Self::Attr)
pub const fn is_unbound_attr(&self) -> bool {
matches!(self, Self::UnboundAttr)
}
pub const fn is_method(&self) -> bool {
matches!(self, Self::Method)
pub const fn is_bound_attr(&self) -> bool {
matches!(self, Self::BoundAttr)
}
pub fn matches(&self, vi: &VarInfo) -> bool {
match self {
Self::Name | Self::BoundAttr => true,
Self::UnboundAttr => !vi.kind.is_instance_attr(),
}
}
}

View file

@ -420,7 +420,7 @@ impl Context {
) -> Triple<VarInfo, TyCheckError> {
if let Some(vi) = self.get_current_scope_var(&ident.name) {
match self.validate_visibility(ident, vi, input, namespace) {
Ok(()) => {
Ok(()) if acc_kind.matches(vi) => {
return Triple::Ok(vi.clone());
}
Err(err) => {
@ -428,6 +428,7 @@ impl Context {
return Triple::Err(err);
}
}
_ => {}
}
} else if let Some((name, _vi)) = self
.future_defined_locals
@ -453,6 +454,17 @@ impl Context {
self.get_similar_name(ident.inspect()),
));
}
for (_, method_ctx) in self.methods_list.iter() {
match method_ctx.rec_get_var_info(ident, acc_kind, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
Triple::Err(e) => {
return Triple::Err(e);
}
Triple::None => {}
}
}
if acc_kind.is_local() {
if let Some(parent) = self.get_outer().or_else(|| self.get_builtins()) {
return parent.rec_get_var_info(ident, acc_kind, input, namespace);
@ -468,7 +480,7 @@ impl Context {
) -> Option<&mut VarInfo> {
if let Some(vi) = self.get_current_scope_var(&ident.name) {
match self.validate_visibility(ident, vi, &self.cfg.input, self) {
Ok(()) => {
Ok(()) if acc_kind.matches(vi) => {
let vi = self.get_mut_current_scope_var(&ident.name).unwrap();
return Some(vi);
}
@ -477,6 +489,7 @@ impl Context {
return None;
}
}
_ => {}
}
}
if acc_kind.is_local() {
@ -500,7 +513,7 @@ impl Context {
.or_else(|| self.future_defined_locals.get(&ident.inspect()[..]))
{
match self.validate_visibility(ident, vi, input, namespace) {
Ok(()) => {
Ok(()) if acc_kind.matches(vi) => {
return Triple::Ok(vi.clone());
}
Err(err) => {
@ -508,6 +521,7 @@ impl Context {
return Triple::Err(err);
}
}
_ => {}
}
}
if acc_kind.is_local() {
@ -562,9 +576,10 @@ impl Context {
}
_ => {}
}
// class/module attr
if let Ok(singular_ctxs) = self.get_singular_ctxs_by_hir_expr(obj, namespace) {
for ctx in singular_ctxs {
match ctx.rec_get_var_info(ident, AccessKind::Attr, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::UnboundAttr, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
@ -575,7 +590,8 @@ impl Context {
}
}
}
match self.get_attr_from_nominal_t(obj, ident, input, namespace) {
// bound method/instance attr
match self.get_bound_attr_from_nominal_t(obj, ident, input, namespace) {
Triple::Ok(vi) => {
if let Some(self_t) = vi.t.self_t() {
match self
@ -636,7 +652,7 @@ impl Context {
Triple::None
}
fn get_attr_from_nominal_t(
fn get_bound_attr_from_nominal_t(
&self,
obj: &hir::Expr,
ident: &Identifier,
@ -646,7 +662,7 @@ impl Context {
let self_t = obj.t();
if let Some(sups) = self.get_nominal_super_type_ctxs(&self_t) {
for ctx in sups {
match ctx.rec_get_var_info(ident, AccessKind::Attr, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
@ -657,7 +673,7 @@ impl Context {
}
// if self is a methods context
if let Some(ctx) = self.get_same_name_context(&ctx.name) {
match ctx.rec_get_var_info(ident, AccessKind::Method, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
@ -691,7 +707,7 @@ impl Context {
}
};
for ctx in ctxs {
match ctx.rec_get_var_info(ident, AccessKind::Attr, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(vi) => {
obj.ref_t().coerce();
return Triple::Ok(vi);
@ -702,7 +718,7 @@ impl Context {
_ => {}
}
if let Some(ctx) = self.get_same_name_context(&ctx.name) {
match ctx.rec_get_var_info(ident, AccessKind::Method, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
@ -946,7 +962,7 @@ impl Context {
}
}
if let Some(ctx) = self.get_same_name_context(&ctx.name) {
match ctx.rec_get_var_info(attr_name, AccessKind::Method, input, namespace) {
match ctx.rec_get_var_info(attr_name, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(t) => {
return Ok(t);
}
@ -3072,4 +3088,58 @@ impl Context {
)))
}
}
pub(crate) fn get_instance_attr(&self, name: &str) -> Option<&VarInfo> {
if let Some(vi) = self.locals.get(name) {
if vi.kind.is_instance_attr() {
return Some(vi);
}
}
if let Some(vi) = self.decls.get(name) {
if vi.kind.is_instance_attr() {
return Some(vi);
}
}
if self.kind.is_method_def() {
self.get_nominal_type_ctx(&mono(&self.name))
.and_then(|(_, ctx)| ctx.get_instance_attr(name))
} else {
self.methods_list.iter().find_map(|(_, ctx)| {
if ctx.kind.is_trait_impl() {
None
} else {
ctx.get_instance_attr(name)
}
})
}
}
/// does not remove instance attribute declarations
pub(crate) fn remove_class_attr(&mut self, name: &str) -> Option<(VarName, VarInfo)> {
if let Some((k, v)) = self.locals.remove_entry(name) {
if v.kind.is_instance_attr() {
self.locals.insert(k, v);
} else {
return Some((k, v));
}
} else if let Some((k, v)) = self.decls.remove_entry(name) {
if v.kind.is_instance_attr() {
self.decls.insert(k, v);
} else {
return Some((k, v));
}
}
if self.kind.is_method_def() {
self.get_mut_nominal_type_ctx(&mono(&self.name))
.and_then(|(_, ctx)| ctx.remove_class_attr(name))
} else {
self.methods_list.iter_mut().find_map(|(_, ctx)| {
if ctx.kind.is_trait_impl() {
None
} else {
ctx.remove_class_attr(name)
}
})
}
}
}

View file

@ -319,6 +319,10 @@ impl ContextKind {
matches!(self, Self::MethodDefs(_))
}
pub const fn is_trait_impl(&self) -> bool {
matches!(self, Self::MethodDefs(Some(_)))
}
pub const fn is_type(&self) -> bool {
matches!(self, Self::Class | Self::Trait | Self::StructuralTrait)
}

View file

@ -9,6 +9,7 @@ use std::time::{Duration, SystemTime};
use erg_common::config::ErgMode;
use erg_common::consts::{ERG_MODE, PYTHON_MODE};
use erg_common::dict::Dict;
use erg_common::env::{is_pystd_main_module, is_std_decl_path};
use erg_common::erg_util::BUILTIN_ERG_MODS;
use erg_common::levenshtein::get_similar_name;
@ -32,7 +33,7 @@ use crate::ty::free::{Constraint, HasLevel};
use crate::ty::typaram::TyParam;
use crate::ty::value::{GenTypeObj, TypeObj, ValueObj};
use crate::ty::{
GuardType, HasType, ParamTy, SubrType, Type, Variable, Visibility, VisibilityModifier,
Field, GuardType, HasType, ParamTy, SubrType, Type, Variable, Visibility, VisibilityModifier,
};
use crate::build_hir::HIRBuilder;
@ -200,7 +201,10 @@ impl Context {
} else {
None
};
if let Some(_decl) = self.decls.remove(&ident.name) {
if self
.remove_class_attr(ident.name.inspect())
.is_some_and(|(_, decl)| !decl.kind.is_auto())
{
Err(TyCheckErrors::from(TyCheckError::duplicate_decl_error(
self.cfg.input.clone(),
line!() as usize,
@ -266,7 +270,10 @@ impl Context {
self.absolutize(sig.ident.name.loc()),
);
self.index().register(&vi);
if let Some(_decl) = self.decls.remove(name) {
if self
.remove_class_attr(name)
.is_some_and(|(_, decl)| !decl.kind.is_auto())
{
Err(TyCheckErrors::from(TyCheckError::duplicate_decl_error(
self.cfg.input.clone(),
line!() as usize,
@ -1347,16 +1354,7 @@ impl Context {
..
} = additional
{
for (field, t) in rec.iter() {
let varname = VarName::from_str(field.symbol.clone());
let vi = VarInfo::instance_attr(
field.clone(),
t.clone(),
self.impl_of(),
ctx.name.clone(),
);
ctx.decls.insert(varname, vi);
}
self.register_instance_attrs(&mut ctx, rec)?;
}
param_t
.map(|t| self.intersection(t, additional.typ()))
@ -1418,16 +1416,7 @@ impl Context {
self.level,
);
let Some(TypeObj::Builtin{ t: Type::Record(req), .. }) = gen.base_or_sup() else { todo!("{gen}") };
for (field, t) in req.iter() {
let vi = VarInfo::instance_attr(
field.clone(),
t.clone(),
self.impl_of(),
ctx.name.clone(),
);
ctx.decls
.insert(VarName::from_str(field.symbol.clone()), vi);
}
self.register_instance_attrs(&mut ctx, req)?;
self.register_gen_mono_type(ident, gen, ctx, Const)
} else {
feature_error!(
@ -1460,16 +1449,7 @@ impl Context {
None
};
if let Some(additional) = additional {
for (field, t) in additional.iter() {
let vi = VarInfo::instance_attr(
field.clone(),
t.clone(),
self.impl_of(),
ctx.name.clone(),
);
ctx.decls
.insert(VarName::from_str(field.symbol.clone()), vi);
}
self.register_instance_attrs(&mut ctx, additional)?;
}
for sup in super_classes.into_iter() {
if let Some((_, sup_ctx)) = self.get_nominal_type_ctx(&sup) {
@ -1521,6 +1501,29 @@ impl Context {
}
}
fn register_instance_attrs(
&self,
ctx: &mut Context,
rec: &Dict<Field, Type>,
) -> CompileResult<()> {
for (field, t) in rec.iter() {
let varname = VarName::from_str(field.symbol.clone());
let vi =
VarInfo::instance_attr(field.clone(), t.clone(), self.impl_of(), ctx.name.clone());
// self.index().register(&vi);
if let Some(_ent) = ctx.decls.insert(varname.clone(), vi) {
return Err(CompileErrors::from(CompileError::duplicate_decl_error(
self.cfg.input.clone(),
line!() as usize,
varname.loc(),
self.caused_by(),
varname.inspect(),
)));
}
}
Ok(())
}
fn gen_class_new_method(&self, gen: &GenTypeObj, ctx: &mut Context) -> CompileResult<()> {
let mut methods = Self::methods(None, self.cfg.clone(), self.shared.clone(), 2, self.level);
let new_t = if let Some(base) = gen.base_or_sup() {
@ -1529,16 +1532,7 @@ impl Context {
t: Type::Record(rec),
..
} => {
for (field, t) in rec.iter() {
let varname = VarName::from_str(field.symbol.clone());
let vi = VarInfo::instance_attr(
field.clone(),
t.clone(),
self.impl_of(),
ctx.name.clone(),
);
ctx.decls.insert(varname, vi);
}
self.register_instance_attrs(ctx, rec)?;
}
other => {
methods.register_fixed_auto_impl(

View file

@ -177,7 +177,7 @@ impl ASTLowerer {
for ctx in ctxs {
if let Triple::Ok(vi) = ctx.rec_get_var_info(
&ident.raw,
AccessKind::Attr,
AccessKind::UnboundAttr,
self.input(),
&self.module.context,
) {

View file

@ -485,11 +485,12 @@ impl LowerError {
) -> Self {
let hint = similar_name.map(|n| {
let vis = similar_info.map_or("".into(), |vi| vi.vis.modifier.display());
let kind = similar_info.map_or("", |vi| vi.kind.display());
switch_lang!(
"japanese" => format!("似た名前の{vis}属性があります: {n}"),
"simplified_chinese" => format!("具有相同名称的{vis}属性: {n}"),
"traditional_chinese" => format!("具有相同名稱的{vis}屬性: {n}"),
"english" => format!("has a similar name {vis} attribute: {n}"),
"japanese" => format!("似た名前の{vis}{kind}属性があります: {n}"),
"simplified_chinese" => format!("具有相同名称的{vis}{kind}属性: {n}"),
"traditional_chinese" => format!("具有相同名稱的{vis}{kind}屬性: {n}"),
"english" => format!("has a similar name {vis} {kind} attribute: {n}"),
)
});
let found = StyledString::new(name, Some(ERR), Some(ATTR));
@ -1157,4 +1158,30 @@ impl LowerWarning {
caused_by,
)
}
pub fn same_name_instance_attr_warning(
input: Input,
errno: usize,
loc: Location,
caused_by: String,
name: &str,
) -> Self {
let name = StyledStr::new(readable_name(name), Some(WARN), Some(ATTR));
Self::new(
ErrorCore::new(
vec![SubMessage::only_loc(loc)],
switch_lang!(
"japanese" => format!("同名のインスタンス属性{name}が存在します"),
"simplified_chinese" => format!("同名的实例属性{name}已存在"),
"traditional_chinese" => format!("同名的實例屬性{name}已存在"),
"english" => format!("an instance attribute named {name} already exists"),
),
errno,
NameWarning,
loc,
),
input,
caused_by,
)
}
}

View file

@ -3,7 +3,7 @@ use std::path::PathBuf;
use erg_common::error::Location;
use erg_common::set::Set;
use erg_common::Str;
use erg_common::{switch_lang, Str};
use erg_parser::ast::DefId;
@ -39,6 +39,7 @@ use Mutability::*;
pub enum VarKind {
Defined(DefId),
Declared,
InstanceAttr,
Parameter {
def_id: DefId,
var: bool,
@ -88,6 +89,38 @@ impl VarKind {
pub const fn is_builtin(&self) -> bool {
matches!(self, Self::Builtin)
}
pub const fn is_auto(&self) -> bool {
matches!(self, Self::Auto)
}
pub const fn is_instance_attr(&self) -> bool {
matches!(self, Self::InstanceAttr)
}
pub const fn display(&self) -> &'static str {
match self {
Self::Auto | Self::FixedAuto => switch_lang!(
"japanese" => "自動",
"simplified_chinese" => "自动",
"traditional_chinese" => "自動",
"english" => "auto",
),
Self::Builtin => switch_lang!(
"japanese" => "組み込み",
"simplified_chinese" => "内置",
"traditional_chinese" => "內置",
"english" => "builtin",
),
Self::InstanceAttr => switch_lang!(
"japanese" => "インスタンス",
"simplified_chinese" => "实例",
"traditional_chinese" => "實例",
"english" => "instance",
),
_ => "",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -285,12 +318,11 @@ impl VarInfo {
} else {
Mutability::Immutable
};
let kind = VarKind::Declared;
Self::new(
t,
muty,
Visibility::new(field.vis, namespace),
kind,
VarKind::InstanceAttr,
None,
impl_of,
None,

View file

@ -0,0 +1,9 @@
C = Class { .x = Int; .y = Int }
C.
x = "aa"
_: Str = C.x
_ = C.y # ERR
c = C.new({.x = 1; .y = 2})
_: Int = c.x
_: Int = c.y

View file

@ -273,6 +273,11 @@ fn exec_assert_cast() -> Result<(), ()> {
expect_failure("examples/assert_cast.er", 0, 3)
}
#[test]
fn exec_class_attr_err() -> Result<(), ()> {
expect_failure("tests/should_err/class_attr.er", 1, 1)
}
#[test]
fn exec_collection_err() -> Result<(), ()> {
expect_failure("tests/should_err/collection.er", 0, 4)