mirror of
https://github.com/erg-lang/erg.git
synced 2025-10-03 05:54:33 +00:00
fix: module accessibility
This commit is contained in:
parent
379caacce9
commit
829b0d11b6
12 changed files with 191 additions and 25 deletions
|
@ -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 =
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
1
tests/should_err/foo/__init__.er
Normal file
1
tests/should_err/foo/__init__.er
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.bar = import "./bar"
|
1
tests/should_err/foo/bar.er
Normal file
1
tests/should_err/foo/bar.er
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.i = 1
|
1
tests/should_err/foo/baz/__init__.er
Normal file
1
tests/should_err/foo/baz/__init__.er
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.i = 1
|
1
tests/should_err/foo/qux.er
Normal file
1
tests/should_err/foo/qux.er
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.i = 1
|
4
tests/should_err/import.er
Normal file
4
tests/should_err/import.er
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
_ = import "foo" # OK
|
||||||
|
_ = import "foo/bar" # OK
|
||||||
|
_ = import "foo/baz" # ERR
|
||||||
|
_ = import "foo/qux" # ERR
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue