fix: module accessibility

This commit is contained in:
Shunsuke Shibayama 2023-05-21 22:35:44 +09:00
parent 379caacce9
commit 829b0d11b6
12 changed files with 191 additions and 25 deletions

View file

@ -50,7 +50,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
if let Some(path) = self if let Some(path) = self
.get_local_ctx(&uri, pos) .get_local_ctx(&uri, pos)
.first() .first()
.and_then(|ctx| ctx.get_path_from_mod_t(&vi.t)) .and_then(|ctx| ctx.get_path_with_mod_t(&vi.t))
{ {
let mod_uri = Url::from_file_path(path).unwrap(); let mod_uri = Url::from_file_path(path).unwrap();
let resp = GotoDefinitionResponse::Array(vec![ let resp = GotoDefinitionResponse::Array(vec![
@ -67,7 +67,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
if let Some((_, vi)) = self if let Some((_, vi)) = self
.get_local_ctx(&uri, pos) .get_local_ctx(&uri, pos)
.first() .first()
.and_then(|ctx| ctx.get_mod_from_t(mod_t)) .and_then(|ctx| ctx.get_mod_with_t(mod_t))
.and_then(|mod_ctx| mod_ctx.get_var_info(token.inspect())) .and_then(|mod_ctx| mod_ctx.get_var_info(token.inspect()))
{ {
let def_uri = let def_uri =

View file

@ -1,5 +1,74 @@
use std::ffi::OsStr;
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DirKind {
ErgModule,
PyModule,
Other,
NotDir,
}
impl From<&Path> for DirKind {
fn from(path: &Path) -> Self {
let Ok(dir) = path.read_dir() else {
return DirKind::NotDir;
};
for ent in dir {
let Ok(ent) = ent else {
continue;
};
if ent.path().file_name() == Some(OsStr::new("__init__.er")) {
return DirKind::ErgModule;
} else if ent.path().file_name() == Some(OsStr::new("__init__.py")) {
return DirKind::PyModule;
}
}
DirKind::Other
}
}
impl DirKind {
pub const fn is_erg_module(&self) -> bool {
matches!(self, DirKind::ErgModule)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FileKind {
InitEr,
InitPy,
Er,
Py,
Other,
NotFile,
}
impl From<&Path> for FileKind {
fn from(path: &Path) -> Self {
if path.is_file() {
match path.file_name() {
Some(name) if name == OsStr::new("__init__.er") => FileKind::InitEr,
Some(name) if name == OsStr::new("__init__.py") => FileKind::InitPy,
Some(name) if name.to_string_lossy().ends_with(".er") => FileKind::Er,
Some(name) if name.to_string_lossy().ends_with(".py") => FileKind::Py,
_ => FileKind::Other,
}
} else {
FileKind::NotFile
}
}
}
impl FileKind {
pub const fn is_init_er(&self) -> bool {
matches!(self, FileKind::InitEr)
}
pub const fn is_simple_erg_file(&self) -> bool {
matches!(self, FileKind::Er)
}
}
pub fn is_cur_dir<P: AsRef<Path>>(path: P) -> bool { pub fn is_cur_dir<P: AsRef<Path>>(path: P) -> bool {
path.as_ref() path.as_ref()
.components() .components()

View file

@ -2455,6 +2455,12 @@ impl Context {
} }
} }
pub(crate) fn get_mod_with_path(&self, path: &Path) -> Option<&Context> {
(self.cfg.input.path() == Some(path)) // module itself
.then_some(self)
.or(self.mod_cache().get(path).map(|ent| &ent.module.context))
}
// FIXME: 現在の実装だとimportしたモジュールはどこからでも見れる // FIXME: 現在の実装だとimportしたモジュールはどこからでも見れる
pub(crate) fn get_mod(&self, name: &str) -> Option<&Context> { pub(crate) fn get_mod(&self, name: &str) -> Option<&Context> {
if name == "module" && ERG_MODE { if name == "module" && ERG_MODE {
@ -2463,15 +2469,15 @@ impl Context {
self.get_builtins() self.get_builtins()
} else { } else {
let t = self.get_var_info(name).map(|(_, vi)| &vi.t)?; let t = self.get_var_info(name).map(|(_, vi)| &vi.t)?;
self.get_mod_from_t(t) self.get_mod_with_t(t)
} }
} }
pub fn get_mod_from_t(&self, mod_t: &Type) -> Option<&Context> { pub fn get_mod_with_t(&self, mod_t: &Type) -> Option<&Context> {
self.get_ctx_from_path(&self.get_path_from_mod_t(mod_t)?) self.get_ctx_from_path(&self.get_path_with_mod_t(mod_t)?)
} }
pub fn get_path_from_mod_t(&self, mod_t: &Type) -> Option<PathBuf> { pub fn get_path_with_mod_t(&self, mod_t: &Type) -> Option<PathBuf> {
let tps = mod_t.typarams(); let tps = mod_t.typarams();
let Some(TyParam::Value(ValueObj::Str(path))) = tps.get(0) else { let Some(TyParam::Value(ValueObj::Str(path))) = tps.get(0) else {
return None; return None;

View file

@ -10,6 +10,7 @@ use erg_common::consts::PYTHON_MODE;
use erg_common::env::is_pystd_main_module; use erg_common::env::is_pystd_main_module;
use erg_common::erg_util::BUILTIN_ERG_MODS; use erg_common::erg_util::BUILTIN_ERG_MODS;
use erg_common::levenshtein::get_similar_name; use erg_common::levenshtein::get_similar_name;
use erg_common::pathutil::{DirKind, FileKind};
use erg_common::python_util::BUILTIN_PYTHON_MODS; use erg_common::python_util::BUILTIN_PYTHON_MODS;
use erg_common::set::Set; use erg_common::set::Set;
use erg_common::traits::{Locational, Stream}; use erg_common::traits::{Locational, Stream};
@ -931,7 +932,9 @@ impl Context {
let Ok(mod_name) = hir::Literal::try_from(mod_name.token.clone()) else { let Ok(mod_name) = hir::Literal::try_from(mod_name.token.clone()) else {
return Ok(()); return Ok(());
}; };
let res = self.import_mod(call.additional_operation().unwrap(), &mod_name); let res = self
.import_mod(call.additional_operation().unwrap(), &mod_name)
.map(|_path| ());
let arg = TyParam::Value(ValueObj::Str( let arg = TyParam::Value(ValueObj::Str(
mod_name.token.content.replace('\"', "").into(), mod_name.token.content.replace('\"', "").into(),
)); ));
@ -946,14 +949,14 @@ impl Context {
params: vec![arg], params: vec![arg],
} }
}; };
let Some(ident) = def.sig.ident() else { return Ok(()) }; let Some(ident) = def.sig.ident() else { return res };
let Some((_, vi)) = self.get_var_info(ident.inspect()) else { let Some((_, vi)) = self.get_var_info(ident.inspect()) else {
return Ok(()); return res;
}; };
if let Some(_fv) = vi.t.as_free() { if let Some(_fv) = vi.t.as_free() {
vi.t.link(&typ); vi.t.link(&typ);
} }
res.map(|_| ()) res
} }
pub(crate) fn preregister_def(&mut self, def: &ast::Def) -> TyCheckResult<()> { pub(crate) fn preregister_def(&mut self, def: &ast::Def) -> TyCheckResult<()> {
@ -1807,24 +1810,73 @@ impl Context {
} }
fn import_erg_mod(&self, __name__: &Str, loc: &impl Locational) -> CompileResult<PathBuf> { fn import_erg_mod(&self, __name__: &Str, loc: &impl Locational) -> CompileResult<PathBuf> {
let mod_cache = self.mod_cache();
let path = match self.cfg.input.resolve_real_path(Path::new(&__name__[..])) { let path = match self.cfg.input.resolve_real_path(Path::new(&__name__[..])) {
Some(path) => path, Some(path) => path,
None => { None => {
return Err(self.import_err(__name__, loc)); return Err(self.import_err(__name__, loc));
} }
}; };
// module itself self.check_mod_vis(path.as_path(), __name__, loc)?;
if self.cfg.input.path() == Some(path.as_path()) {
return Ok(path);
}
if let Some(referrer) = self.cfg.input.path() { if let Some(referrer) = self.cfg.input.path() {
let graph = &self.shared.as_ref().unwrap().graph; let graph = &self.shared.as_ref().unwrap().graph;
graph.inc_ref(referrer, path.clone()); graph.inc_ref(referrer, path.clone());
} }
if mod_cache.get(&path).is_some() { if self.get_mod_with_path(&path).is_some() {
return Ok(path); return Ok(path);
} }
self.build_erg_mod(path, __name__, loc)
}
/// If the path is like `foo/bar`, check if `bar` is a public module (the definition is in `foo/__init__.er`)
fn check_mod_vis(
&self,
path: &Path,
__name__: &Str,
loc: &impl Locational,
) -> CompileResult<()> {
if let Some(parent) = path.parent() {
if FileKind::from(path).is_simple_erg_file() && DirKind::from(parent).is_erg_module() {
let parent = parent.join("__init__.er");
let parent_module = if let Some(parent) = self.get_mod_with_path(&parent) {
Some(parent)
} else {
// TODO: This error should not be discarded, but the later processing should also continue while generating the error
self.build_erg_mod(parent.clone(), __name__, loc)?;
self.get_mod_with_path(&parent)
};
if let Some(parent_module) = parent_module {
let import_err = || {
TyCheckErrors::from(TyCheckError::import_error(
self.cfg.input.clone(),
line!() as usize,
format!("module `{__name__}` is not public"),
loc.loc(),
self.caused_by(),
None,
None,
))
};
let mod_name = path.file_stem().unwrap_or_default().to_string_lossy();
if let Some((_, vi)) = parent_module.get_var_info(&mod_name) {
if !vi.vis.compatible(&ast::AccessModifier::Public, self) {
return Err(import_err());
}
} else {
return Err(import_err());
}
}
}
}
Ok(())
}
fn build_erg_mod(
&self,
path: PathBuf,
__name__: &Str,
loc: &impl Locational,
) -> CompileResult<PathBuf> {
let mod_cache = self.mod_cache();
let mut cfg = self.cfg.inherit(path.clone()); let mut cfg = self.cfg.inherit(path.clone());
let src = cfg let src = cfg
.input .input
@ -1839,15 +1891,15 @@ impl Context {
Some(artifact.object), Some(artifact.object),
builder.pop_mod_ctx().unwrap(), builder.pop_mod_ctx().unwrap(),
); );
Ok(path)
} }
Err(artifact) => { Err(artifact) => {
if let Some(hir) = artifact.object { if let Some(hir) = artifact.object {
mod_cache.register(path, Some(hir), builder.pop_mod_ctx().unwrap()); mod_cache.register(path, Some(hir), builder.pop_mod_ctx().unwrap());
} }
return Err(artifact.errors); Err(artifact.errors)
} }
} }
Ok(path)
} }
fn similar_builtin_py_mod_name(&self, name: &Str) -> Option<Str> { fn similar_builtin_py_mod_name(&self, name: &Str) -> Option<Str> {
@ -1974,6 +2026,16 @@ impl Context {
if py_mod_cache.get(&path).is_some() { if py_mod_cache.get(&path).is_some() {
return Ok(path); return Ok(path);
} }
self.build_decl_mod(path, __name__, loc)
}
fn build_decl_mod(
&self,
path: PathBuf,
__name__: &Str,
loc: &impl Locational,
) -> CompileResult<PathBuf> {
let py_mod_cache = self.py_mod_cache();
let mut cfg = self.cfg.inherit(path.clone()); let mut cfg = self.cfg.inherit(path.clone());
let src = cfg let src = cfg
.input .input
@ -1988,15 +2050,15 @@ impl Context {
Ok(artifact) => { Ok(artifact) => {
let ctx = builder.pop_mod_ctx().unwrap(); let ctx = builder.pop_mod_ctx().unwrap();
py_mod_cache.register(path.clone(), Some(artifact.object), ctx); py_mod_cache.register(path.clone(), Some(artifact.object), ctx);
Ok(path)
} }
Err(artifact) => { Err(artifact) => {
if let Some(hir) = artifact.object { if let Some(hir) = artifact.object {
py_mod_cache.register(path, Some(hir), builder.pop_mod_ctx().unwrap()); py_mod_cache.register(path, Some(hir), builder.pop_mod_ctx().unwrap());
} }
return Err(artifact.errors); Err(artifact.errors)
} }
} }
Ok(path)
} }
pub fn del(&mut self, ident: &hir::Identifier) -> CompileResult<()> { pub fn del(&mut self, ident: &hir::Identifier) -> CompileResult<()> {

View file

@ -42,12 +42,19 @@ Now the `bar` directory is recognized as a module. If the only file in `bar` is
└─┬ bar └─┬ bar
├─ __init__.er ├─ __init__.er
├─ baz.er ├─ baz.er
└─ qux.er └─┬ qux
└─ __init__.er
```
```python
# bar/__init__.er
.baz = import "./baz"
.qux = import "./qux"
``` ```
From outside the `bar` directory, you can use like the following. From outside the `bar` directory, you can use like the following.
```erg ```python
bar = import "bar" bar = import "bar"
bar.baz.p!() bar.baz.p!()
@ -55,11 +62,12 @@ bar.qux.p!()
``` ```
`__init__.er` is not just a marker that makes a directory as a module, it also controls the visibility of the module. `__init__.er` is not just a marker that makes a directory as a module, it also controls the visibility of the module.
Unlike Python, Erg modules are not importable by default.
```erg ```python
# __init__.er # __init__.er
# `. /` points to the current directory. It can be omitted # `./` points to the current directory. It can be omitted
.baz = import ". /baz" .baz = import ". /baz"
qux = import ". /qux" qux = import ". /qux"

View file

@ -44,7 +44,14 @@ assert foo.i == 1
└─┬ bar └─┬ bar
├─ __init__.er ├─ __init__.er
├─ baz.er ├─ baz.er
└─ qux.er └─┬ qux
└─ __init__.er
```
```python
# bar/__init__.er
.baz = import "./baz"
.qux = import "./qux"
``` ```
`bar`ディレクトリの外側からは以下のようにして使用できます。 `bar`ディレクトリの外側からは以下のようにして使用できます。
@ -57,6 +64,7 @@ bar.qux.p!()
``` ```
`__init__.er`は単にディレクトリをモジュールとして機能させるだけのマーカーではなく、モジュールの可視性を制御する役割も持ちます。 `__init__.er`は単にディレクトリをモジュールとして機能させるだけのマーカーではなく、モジュールの可視性を制御する役割も持ちます。
Pythonとは違い、Ergのモジュールはデフォルトではインポートできないようになっています。
```python ```python
# __init__.er # __init__.er

View file

@ -0,0 +1 @@
.bar = import "./bar"

View file

@ -0,0 +1 @@
.i = 1

View file

@ -0,0 +1 @@
.i = 1

View file

@ -0,0 +1 @@
.i = 1

View file

@ -0,0 +1,4 @@
_ = import "foo" # OK
_ = import "foo/bar" # OK
_ = import "foo/baz" # ERR
_ = import "foo/qux" # ERR

View file

@ -283,6 +283,11 @@ fn exec_impl_err() -> Result<(), ()> {
expect_failure("tests/should_err/impl.er", 2, 2) expect_failure("tests/should_err/impl.er", 2, 2)
} }
#[test]
fn exec_import_err() -> Result<(), ()> {
expect_failure("tests/should_err/import.er", 0, 2)
}
#[test] #[test]
fn exec_infer_union_array() -> Result<(), ()> { fn exec_infer_union_array() -> Result<(), ()> {
expect_failure("tests/should_err/infer_union_array.er", 2, 1) expect_failure("tests/should_err/infer_union_array.er", 2, 1)