mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Resolve bound name of bare import statically (#5773)
This commit is contained in:
parent
9665eecdb6
commit
1b2719c94c
@ -6,7 +6,7 @@ use typst_library::diag::{
|
|||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Content, Module, Value};
|
use typst_library::foundations::{Content, Module, Value};
|
||||||
use typst_library::World;
|
use typst_library::World;
|
||||||
use typst_syntax::ast::{self, AstNode};
|
use typst_syntax::ast::{self, AstNode, BareImportError};
|
||||||
use typst_syntax::package::{PackageManifest, PackageSpec};
|
use typst_syntax::package::{PackageManifest, PackageSpec};
|
||||||
use typst_syntax::{FileId, Span, VirtualPath};
|
use typst_syntax::{FileId, Span, VirtualPath};
|
||||||
|
|
||||||
@ -16,11 +16,11 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let source = self.source();
|
let source_expr = self.source();
|
||||||
let source_span = source.span();
|
let source_span = source_expr.span();
|
||||||
let mut source = source.eval(vm)?;
|
|
||||||
let new_name = self.new_name();
|
let mut source = source_expr.eval(vm)?;
|
||||||
let imports = self.imports();
|
let mut is_str = false;
|
||||||
|
|
||||||
match &source {
|
match &source {
|
||||||
Value::Func(func) => {
|
Value::Func(func) => {
|
||||||
@ -32,6 +32,7 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
Value::Module(_) => {}
|
Value::Module(_) => {}
|
||||||
Value::Str(path) => {
|
Value::Str(path) => {
|
||||||
source = Value::Module(import(&mut vm.engine, path, source_span)?);
|
source = Value::Module(import(&mut vm.engine, path, source_span)?);
|
||||||
|
is_str = true;
|
||||||
}
|
}
|
||||||
v => {
|
v => {
|
||||||
bail!(
|
bail!(
|
||||||
@ -42,9 +43,12 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Source itself is imported if there is no import list or a rename.
|
||||||
|
let bare_name = self.bare_name();
|
||||||
|
let new_name = self.new_name();
|
||||||
if let Some(new_name) = new_name {
|
if let Some(new_name) = new_name {
|
||||||
if let ast::Expr::Ident(ident) = self.source() {
|
if let Ok(source_name) = &bare_name {
|
||||||
if ident.as_str() == new_name.as_str() {
|
if source_name == new_name.as_str() {
|
||||||
// Warn on `import x as x`
|
// Warn on `import x as x`
|
||||||
vm.engine.sink.warn(warning!(
|
vm.engine.sink.warn(warning!(
|
||||||
new_name.span(),
|
new_name.span(),
|
||||||
@ -58,12 +62,33 @@ impl Eval for ast::ModuleImport<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let scope = source.scope().unwrap();
|
let scope = source.scope().unwrap();
|
||||||
match imports {
|
match self.imports() {
|
||||||
None => {
|
None => {
|
||||||
// Only import here if there is no rename.
|
|
||||||
if new_name.is_none() {
|
if new_name.is_none() {
|
||||||
let name: EcoString = source.name().unwrap().into();
|
match self.bare_name() {
|
||||||
vm.scopes.top.define(name, source);
|
// Bare dynamic string imports are not allowed.
|
||||||
|
Ok(name)
|
||||||
|
if !is_str || matches!(source_expr, ast::Expr::Str(_)) =>
|
||||||
|
{
|
||||||
|
if matches!(source_expr, ast::Expr::Ident(_)) {
|
||||||
|
vm.engine.sink.warn(warning!(
|
||||||
|
source_expr.span(),
|
||||||
|
"this import has no effect",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
vm.scopes.top.define_spanned(name, source, source_span);
|
||||||
|
}
|
||||||
|
Ok(_) | Err(BareImportError::Dynamic) => bail!(
|
||||||
|
source_span, "dynamic import requires an explicit name";
|
||||||
|
hint: "you can name the import with `as`"
|
||||||
|
),
|
||||||
|
Err(BareImportError::PathInvalid) => bail!(
|
||||||
|
source_span, "module name would not be a valid identifier";
|
||||||
|
hint: "you can rename the import with `as`",
|
||||||
|
),
|
||||||
|
// Bad package spec would have failed the import already.
|
||||||
|
Err(BareImportError::PackageInvalid) => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ast::Imports::Wildcard) => {
|
Some(ast::Imports::Wildcard) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use typst::foundations::{Module, Value};
|
use typst::foundations::{Module, Value};
|
||||||
use typst::syntax::ast::AstNode;
|
use typst::syntax::ast::AstNode;
|
||||||
use typst::syntax::{ast, LinkedNode, Span, SyntaxKind, SyntaxNode};
|
use typst::syntax::{ast, LinkedNode, Span, SyntaxKind};
|
||||||
|
|
||||||
use crate::{analyze_import, IdeWorld};
|
use crate::{analyze_import, IdeWorld};
|
||||||
|
|
||||||
@ -30,38 +30,38 @@ pub fn named_items<T>(
|
|||||||
|
|
||||||
if let Some(v) = node.cast::<ast::ModuleImport>() {
|
if let Some(v) = node.cast::<ast::ModuleImport>() {
|
||||||
let imports = v.imports();
|
let imports = v.imports();
|
||||||
let source = node
|
let source = v.source();
|
||||||
.children()
|
|
||||||
.find(|child| child.is::<ast::Expr>())
|
let source_value = node
|
||||||
.and_then(|source: LinkedNode| {
|
.find(source.span())
|
||||||
Some((analyze_import(world, &source)?, source))
|
.and_then(|source| analyze_import(world, &source));
|
||||||
});
|
let source_value = source_value.as_ref();
|
||||||
let source = source.as_ref();
|
|
||||||
|
let module = source_value.and_then(|value| match value {
|
||||||
|
Value::Module(module) => Some(module),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let name_and_span = match (imports, v.new_name()) {
|
||||||
|
// ```plain
|
||||||
|
// import "foo" as name
|
||||||
|
// import "foo" as name: ..
|
||||||
|
// ```
|
||||||
|
(_, Some(name)) => Some((name.get().clone(), name.span())),
|
||||||
|
// ```plain
|
||||||
|
// import "foo"
|
||||||
|
// ```
|
||||||
|
(None, None) => v.bare_name().ok().map(|name| (name, source.span())),
|
||||||
|
// ```plain
|
||||||
|
// import "foo": ..
|
||||||
|
// ```
|
||||||
|
(Some(..), None) => None,
|
||||||
|
};
|
||||||
|
|
||||||
// Seeing the module itself.
|
// Seeing the module itself.
|
||||||
if let Some((value, source)) = source {
|
if let Some((name, span)) = name_and_span {
|
||||||
let site = match (imports, v.new_name()) {
|
if let Some(res) = recv(NamedItem::Module(&name, span, module)) {
|
||||||
// ```plain
|
return Some(res);
|
||||||
// import "foo" as name;
|
|
||||||
// import "foo" as name: ..;
|
|
||||||
// ```
|
|
||||||
(_, Some(name)) => Some(name.to_untyped()),
|
|
||||||
// ```plain
|
|
||||||
// import "foo";
|
|
||||||
// ```
|
|
||||||
(None, None) => Some(source.get()),
|
|
||||||
// ```plain
|
|
||||||
// import "foo": ..;
|
|
||||||
// ```
|
|
||||||
(Some(..), None) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((site, value)) =
|
|
||||||
site.zip(value.clone().cast::<Module>().ok())
|
|
||||||
{
|
|
||||||
if let Some(res) = recv(NamedItem::Module(&value, site)) {
|
|
||||||
return Some(res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ pub fn named_items<T>(
|
|||||||
// import "foo": *;
|
// import "foo": *;
|
||||||
// ```
|
// ```
|
||||||
Some(ast::Imports::Wildcard) => {
|
Some(ast::Imports::Wildcard) => {
|
||||||
if let Some(scope) = source.and_then(|(value, _)| value.scope()) {
|
if let Some(scope) = source_value.and_then(Value::scope) {
|
||||||
for (name, value, span) in scope.iter() {
|
for (name, value, span) in scope.iter() {
|
||||||
let item = NamedItem::Import(name, span, Some(value));
|
let item = NamedItem::Import(name, span, Some(value));
|
||||||
if let Some(res) = recv(item) {
|
if let Some(res) = recv(item) {
|
||||||
@ -92,7 +92,7 @@ pub fn named_items<T>(
|
|||||||
let bound = item.bound_name();
|
let bound = item.bound_name();
|
||||||
|
|
||||||
let (span, value) = item.path().iter().fold(
|
let (span, value) = item.path().iter().fold(
|
||||||
(bound.span(), source.map(|(value, _)| value)),
|
(bound.span(), source_value),
|
||||||
|(span, value), path_ident| {
|
|(span, value), path_ident| {
|
||||||
let scope = value.and_then(|v| v.scope());
|
let scope = value.and_then(|v| v.scope());
|
||||||
let span = scope
|
let span = scope
|
||||||
@ -175,8 +175,8 @@ pub enum NamedItem<'a> {
|
|||||||
Var(ast::Ident<'a>),
|
Var(ast::Ident<'a>),
|
||||||
/// A function item.
|
/// A function item.
|
||||||
Fn(ast::Ident<'a>),
|
Fn(ast::Ident<'a>),
|
||||||
/// A (imported) module item.
|
/// A (imported) module.
|
||||||
Module(&'a Module, &'a SyntaxNode),
|
Module(&'a EcoString, Span, Option<&'a Module>),
|
||||||
/// An imported item.
|
/// An imported item.
|
||||||
Import(&'a EcoString, Span, Option<&'a Value>),
|
Import(&'a EcoString, Span, Option<&'a Value>),
|
||||||
}
|
}
|
||||||
@ -186,7 +186,7 @@ impl<'a> NamedItem<'a> {
|
|||||||
match self {
|
match self {
|
||||||
NamedItem::Var(ident) => ident.get(),
|
NamedItem::Var(ident) => ident.get(),
|
||||||
NamedItem::Fn(ident) => ident.get(),
|
NamedItem::Fn(ident) => ident.get(),
|
||||||
NamedItem::Module(value, _) => value.name(),
|
NamedItem::Module(name, _, _) => name,
|
||||||
NamedItem::Import(name, _, _) => name,
|
NamedItem::Import(name, _, _) => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,7 +194,7 @@ impl<'a> NamedItem<'a> {
|
|||||||
pub(crate) fn value(&self) -> Option<Value> {
|
pub(crate) fn value(&self) -> Option<Value> {
|
||||||
match self {
|
match self {
|
||||||
NamedItem::Var(..) | NamedItem::Fn(..) => None,
|
NamedItem::Var(..) | NamedItem::Fn(..) => None,
|
||||||
NamedItem::Module(value, _) => Some(Value::Module((*value).clone())),
|
NamedItem::Module(_, _, value) => value.cloned().map(Value::Module),
|
||||||
NamedItem::Import(_, _, value) => value.cloned(),
|
NamedItem::Import(_, _, value) => value.cloned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ impl<'a> NamedItem<'a> {
|
|||||||
pub(crate) fn span(&self) -> Span {
|
pub(crate) fn span(&self) -> Span {
|
||||||
match *self {
|
match *self {
|
||||||
NamedItem::Var(name) | NamedItem::Fn(name) => name.span(),
|
NamedItem::Var(name) | NamedItem::Fn(name) => name.span(),
|
||||||
NamedItem::Module(_, site) => site.span(),
|
NamedItem::Module(_, span, _) => span,
|
||||||
NamedItem::Import(_, span, _) => span,
|
NamedItem::Import(_, span, _) => span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -356,7 +356,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_named_items_import() {
|
fn test_named_items_import() {
|
||||||
test("#import \"foo.typ\": a; #(a);", 2).must_include(["a"]);
|
test("#import \"foo.typ\"", 2).must_include(["foo"]);
|
||||||
|
test("#import \"foo.typ\" as bar", 2)
|
||||||
|
.must_include(["bar"])
|
||||||
|
.must_exclude(["foo"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_named_items_import_items() {
|
||||||
|
test("#import \"foo.typ\": a; #(a);", 2)
|
||||||
|
.must_include(["a"])
|
||||||
|
.must_exclude(["foo"]);
|
||||||
|
|
||||||
let world = TestWorld::new("#import \"foo.typ\": a.b; #(b);")
|
let world = TestWorld::new("#import \"foo.typ\": a.b; #(b);")
|
||||||
.with_source("foo.typ", "#import \"a.typ\"")
|
.with_source("foo.typ", "#import \"a.typ\"")
|
||||||
|
@ -122,8 +122,8 @@ pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) {
|
|||||||
if features.is_enabled(Feature::Html) {
|
if features.is_enabled(Feature::Html) {
|
||||||
global.define_func::<target>();
|
global.define_func::<target>();
|
||||||
}
|
}
|
||||||
global.define_module(calc::module());
|
global.define("calc", calc::module());
|
||||||
global.define_module(sys::module(inputs));
|
global.define("sys", sys::module(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fails with an error.
|
/// Fails with an error.
|
||||||
|
@ -32,7 +32,7 @@ use crate::foundations::{repr, ty, Content, Scope, Value};
|
|||||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
/// The module's name.
|
/// The module's name.
|
||||||
name: EcoString,
|
name: Option<EcoString>,
|
||||||
/// The reference-counted inner fields.
|
/// The reference-counted inner fields.
|
||||||
inner: Arc<Repr>,
|
inner: Arc<Repr>,
|
||||||
}
|
}
|
||||||
@ -52,14 +52,22 @@ impl Module {
|
|||||||
/// Create a new module.
|
/// Create a new module.
|
||||||
pub fn new(name: impl Into<EcoString>, scope: Scope) -> Self {
|
pub fn new(name: impl Into<EcoString>, scope: Scope) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.into(),
|
name: Some(name.into()),
|
||||||
|
inner: Arc::new(Repr { scope, content: Content::empty(), file_id: None }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new anonymous module without a name.
|
||||||
|
pub fn anonymous(scope: Scope) -> Self {
|
||||||
|
Self {
|
||||||
|
name: None,
|
||||||
inner: Arc::new(Repr { scope, content: Content::empty(), file_id: None }),
|
inner: Arc::new(Repr { scope, content: Content::empty(), file_id: None }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the module's name.
|
/// Update the module's name.
|
||||||
pub fn with_name(mut self, name: impl Into<EcoString>) -> Self {
|
pub fn with_name(mut self, name: impl Into<EcoString>) -> Self {
|
||||||
self.name = name.into();
|
self.name = Some(name.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +90,8 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the module's name.
|
/// Get the module's name.
|
||||||
pub fn name(&self) -> &EcoString {
|
pub fn name(&self) -> Option<&EcoString> {
|
||||||
&self.name
|
self.name.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the module's scope.
|
/// Access the module's scope.
|
||||||
@ -105,8 +113,9 @@ impl Module {
|
|||||||
|
|
||||||
/// Try to access a definition in the module.
|
/// Try to access a definition in the module.
|
||||||
pub fn field(&self, name: &str) -> StrResult<&Value> {
|
pub fn field(&self, name: &str) -> StrResult<&Value> {
|
||||||
self.scope().get(name).ok_or_else(|| {
|
self.scope().get(name).ok_or_else(|| match &self.name {
|
||||||
eco_format!("module `{}` does not contain `{name}`", self.name())
|
Some(module) => eco_format!("module `{module}` does not contain `{name}`"),
|
||||||
|
None => eco_format!("module does not contain `{name}`"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +140,10 @@ impl Debug for Module {
|
|||||||
|
|
||||||
impl repr::Repr for Module {
|
impl repr::Repr for Module {
|
||||||
fn repr(&self) -> EcoString {
|
fn repr(&self) -> EcoString {
|
||||||
eco_format!("<module {}>", self.name())
|
match &self.name {
|
||||||
|
Some(module) => eco_format!("<module {module}>"),
|
||||||
|
None => "<module>".into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@ use typst_utils::Static;
|
|||||||
|
|
||||||
use crate::diag::{bail, HintedStrResult, HintedString, StrResult};
|
use crate::diag::{bail, HintedStrResult, HintedString, StrResult};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Element, Func, IntoValue, Module, NativeElement, NativeFunc, NativeFuncData,
|
Element, Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType,
|
||||||
NativeType, Type, Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
use crate::Library;
|
use crate::Library;
|
||||||
|
|
||||||
@ -252,11 +252,6 @@ impl Scope {
|
|||||||
self.define(data.name, Element::from(data));
|
self.define(data.name, Element::from(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a module.
|
|
||||||
pub fn define_module(&mut self, module: Module) {
|
|
||||||
self.define(module.name().clone(), module);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to access a variable immutably.
|
/// Try to access a variable immutably.
|
||||||
pub fn get(&self, var: &str) -> Option<&Value> {
|
pub fn get(&self, var: &str) -> Option<&Value> {
|
||||||
self.map.get(var).map(Slot::read)
|
self.map.get(var).map(Slot::read)
|
||||||
|
@ -181,16 +181,6 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name, if this is a function, type, or module.
|
|
||||||
pub fn name(&self) -> Option<&str> {
|
|
||||||
match self {
|
|
||||||
Self::Func(func) => func.name(),
|
|
||||||
Self::Type(ty) => Some(ty.short_name()),
|
|
||||||
Self::Module(module) => Some(module.name()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to extract documentation for the value.
|
/// Try to extract documentation for the value.
|
||||||
pub fn docs(&self) -> Option<&'static str> {
|
pub fn docs(&self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
@ -730,6 +720,11 @@ mod tests {
|
|||||||
assert_eq!(value.into_value().repr(), exp);
|
assert_eq!(value.into_value().repr(), exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_value_size() {
|
||||||
|
assert!(std::mem::size_of::<Value>() <= 32);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_value_debug() {
|
fn test_value_debug() {
|
||||||
// Primitives.
|
// Primitives.
|
||||||
|
@ -244,7 +244,7 @@ fn global(math: Module, inputs: Dict, features: &Features) -> Module {
|
|||||||
self::model::define(&mut global);
|
self::model::define(&mut global);
|
||||||
self::text::define(&mut global);
|
self::text::define(&mut global);
|
||||||
global.reset_category();
|
global.reset_category();
|
||||||
global.define_module(math);
|
global.define("math", math);
|
||||||
self::layout::define(&mut global);
|
self::layout::define(&mut global);
|
||||||
self::visualize::define(&mut global);
|
self::visualize::define(&mut global);
|
||||||
self::introspection::define(&mut global);
|
self::introspection::define(&mut global);
|
||||||
@ -253,7 +253,7 @@ fn global(math: Module, inputs: Dict, features: &Features) -> Module {
|
|||||||
self::pdf::define(&mut global);
|
self::pdf::define(&mut global);
|
||||||
global.reset_category();
|
global.reset_category();
|
||||||
if features.is_enabled(Feature::Html) {
|
if features.is_enabled(Feature::Html) {
|
||||||
global.define_module(self::html::module());
|
global.define("html", self::html::module());
|
||||||
}
|
}
|
||||||
prelude(&mut global);
|
prelude(&mut global);
|
||||||
Module::new("global", global)
|
Module::new("global", global)
|
||||||
|
@ -13,7 +13,7 @@ pub static PDF: Category;
|
|||||||
/// Hook up the `pdf` module.
|
/// Hook up the `pdf` module.
|
||||||
pub(super) fn define(global: &mut Scope) {
|
pub(super) fn define(global: &mut Scope) {
|
||||||
global.category(PDF);
|
global.category(PDF);
|
||||||
global.define_module(module());
|
global.define("pdf", module());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hook up all `pdf` definitions.
|
/// Hook up all `pdf` definitions.
|
||||||
|
@ -4,11 +4,14 @@
|
|||||||
|
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use crate::{is_newline, Span, SyntaxKind, SyntaxNode};
|
use crate::package::PackageSpec;
|
||||||
|
use crate::{is_ident, is_newline, Span, SyntaxKind, SyntaxNode};
|
||||||
|
|
||||||
/// A typed AST node.
|
/// A typed AST node.
|
||||||
pub trait AstNode<'a>: Sized {
|
pub trait AstNode<'a>: Sized {
|
||||||
@ -2064,6 +2067,41 @@ impl<'a> ModuleImport<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The name that will be bound for a bare import. This name must be
|
||||||
|
/// statically known. It can come from:
|
||||||
|
/// - an identifier
|
||||||
|
/// - a field access
|
||||||
|
/// - a string that is a valid file path where the file stem is a valid
|
||||||
|
/// identifier
|
||||||
|
/// - a string that is a valid package spec
|
||||||
|
pub fn bare_name(self) -> Result<EcoString, BareImportError> {
|
||||||
|
match self.source() {
|
||||||
|
Expr::Ident(ident) => Ok(ident.get().clone()),
|
||||||
|
Expr::FieldAccess(access) => Ok(access.field().get().clone()),
|
||||||
|
Expr::Str(string) => {
|
||||||
|
let string = string.get();
|
||||||
|
let name = if string.starts_with('@') {
|
||||||
|
PackageSpec::from_str(&string)
|
||||||
|
.map_err(|_| BareImportError::PackageInvalid)?
|
||||||
|
.name
|
||||||
|
} else {
|
||||||
|
Path::new(string.as_str())
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|path| path.to_str())
|
||||||
|
.ok_or(BareImportError::PathInvalid)?
|
||||||
|
.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_ident(&name) {
|
||||||
|
return Err(BareImportError::PathInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(name)
|
||||||
|
}
|
||||||
|
_ => Err(BareImportError::Dynamic),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The name this module was assigned to, if it was renamed with `as`
|
/// The name this module was assigned to, if it was renamed with `as`
|
||||||
/// (`renamed` in `import "..." as renamed`).
|
/// (`renamed` in `import "..." as renamed`).
|
||||||
pub fn new_name(self) -> Option<Ident<'a>> {
|
pub fn new_name(self) -> Option<Ident<'a>> {
|
||||||
@ -2074,6 +2112,18 @@ impl<'a> ModuleImport<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reasons why a bare name cannot be determined for an import source.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum BareImportError {
|
||||||
|
/// There is no statically resolvable binding name.
|
||||||
|
Dynamic,
|
||||||
|
/// The import source is not a valid path or the path stem not a valid
|
||||||
|
/// identifier.
|
||||||
|
PathInvalid,
|
||||||
|
/// The import source is not a valid package spec.
|
||||||
|
PackageInvalid,
|
||||||
|
}
|
||||||
|
|
||||||
/// The items that ought to be imported from a file.
|
/// The items that ought to be imported from a file.
|
||||||
#[derive(Debug, Copy, Clone, Hash)]
|
#[derive(Debug, Copy, Clone, Hash)]
|
||||||
pub enum Imports<'a> {
|
pub enum Imports<'a> {
|
||||||
|
@ -145,6 +145,34 @@
|
|||||||
#test(module.item(1, 2), 3)
|
#test(module.item(1, 2), 3)
|
||||||
#test(module.push(2), 3)
|
#test(module.push(2), 3)
|
||||||
|
|
||||||
|
--- import-from-file-bare-invalid ---
|
||||||
|
// Error: 9-33 module name would not be a valid identifier
|
||||||
|
// Hint: 9-33 you can rename the import with `as`
|
||||||
|
#import "modules/with space.typ"
|
||||||
|
|
||||||
|
--- import-from-file-bare-dynamic ---
|
||||||
|
// Error: 9-26 dynamic import requires an explicit name
|
||||||
|
// Hint: 9-26 you can name the import with `as`
|
||||||
|
#import "mod" + "ule.typ"
|
||||||
|
|
||||||
|
--- import-from-var-bare ---
|
||||||
|
#let p = "module.typ"
|
||||||
|
// Error: 9-10 dynamic import requires an explicit name
|
||||||
|
// Hint: 9-10 you can name the import with `as`
|
||||||
|
#import p
|
||||||
|
#test(p.b, 1)
|
||||||
|
|
||||||
|
--- import-from-dict-field-bare ---
|
||||||
|
#let d = (p: "module.typ")
|
||||||
|
// Error: 9-12 dynamic import requires an explicit name
|
||||||
|
// Hint: 9-12 you can name the import with `as`
|
||||||
|
#import d.p
|
||||||
|
#test(p.b, 1)
|
||||||
|
|
||||||
|
--- import-from-file-renamed-dynamic ---
|
||||||
|
#import "mod" + "ule.typ" as mod
|
||||||
|
#test(mod.b, 1)
|
||||||
|
|
||||||
--- import-from-file-renamed ---
|
--- import-from-file-renamed ---
|
||||||
// A renamed module import without items.
|
// A renamed module import without items.
|
||||||
#import "module.typ" as other
|
#import "module.typ" as other
|
||||||
@ -160,6 +188,10 @@
|
|||||||
#test(item(1, 2), 3)
|
#test(item(1, 2), 3)
|
||||||
#test(newname.item(1, 2), 3)
|
#test(newname.item(1, 2), 3)
|
||||||
|
|
||||||
|
--- import-from-function-scope-bare ---
|
||||||
|
// Warning: 9-13 this import has no effect
|
||||||
|
#import enum
|
||||||
|
|
||||||
--- import-from-function-scope-renamed ---
|
--- import-from-function-scope-renamed ---
|
||||||
// Renamed module import with function scopes.
|
// Renamed module import with function scopes.
|
||||||
#import enum as othernum
|
#import enum as othernum
|
||||||
@ -171,6 +203,23 @@
|
|||||||
#import asrt: ne as asne
|
#import asrt: ne as asne
|
||||||
#asne(1, 2)
|
#asne(1, 2)
|
||||||
|
|
||||||
|
--- import-from-module-bare ---
|
||||||
|
#import "modules/chap1.typ" as mymod
|
||||||
|
// Warning: 9-14 this import has no effect
|
||||||
|
#import mymod
|
||||||
|
// The name `chap1` is not bound.
|
||||||
|
// Error: 2-7 unknown variable: chap1
|
||||||
|
#chap1
|
||||||
|
|
||||||
|
--- import-module-nested ---
|
||||||
|
#import std.calc: pi
|
||||||
|
#test(pi, calc.pi)
|
||||||
|
|
||||||
|
--- import-module-nested-bare ---
|
||||||
|
#import "module.typ"
|
||||||
|
#import module.chap2
|
||||||
|
#test(chap2.name, "Peter")
|
||||||
|
|
||||||
--- import-module-item-name-mutating ---
|
--- import-module-item-name-mutating ---
|
||||||
// Edge case for module access that isn't fixed.
|
// Edge case for module access that isn't fixed.
|
||||||
#import "module.typ"
|
#import "module.typ"
|
||||||
@ -214,10 +263,14 @@
|
|||||||
// Warning: 31-35 unnecessary import rename to same name
|
// Warning: 31-35 unnecessary import rename to same name
|
||||||
#import enum as enum: item as item
|
#import enum as enum: item as item
|
||||||
|
|
||||||
--- import-item-rename-unnecessary-but-ok ---
|
--- import-item-rename-unnecessary-string ---
|
||||||
// No warning on a case that isn't obviously pathological
|
// Warning: 25-31 unnecessary import rename to same name
|
||||||
#import "module.typ" as module
|
#import "module.typ" as module
|
||||||
|
|
||||||
|
--- import-item-rename-unnecessary-but-ok ---
|
||||||
|
#import "modul" + "e.typ" as module
|
||||||
|
#test(module.b, 1)
|
||||||
|
|
||||||
--- import-from-closure-invalid ---
|
--- import-from-closure-invalid ---
|
||||||
// Can't import from closures.
|
// Can't import from closures.
|
||||||
#let f(x) = x
|
#let f(x) = x
|
||||||
@ -359,6 +412,15 @@ This is never reached.
|
|||||||
#import "@test/adder:0.1.0"
|
#import "@test/adder:0.1.0"
|
||||||
#test(adder.add(2, 8), 10)
|
#test(adder.add(2, 8), 10)
|
||||||
|
|
||||||
|
--- import-from-package-dynamic ---
|
||||||
|
// Error: 9-33 dynamic import requires an explicit name
|
||||||
|
// Hint: 9-33 you can name the import with `as`
|
||||||
|
#import "@test/" + "adder:0.1.0"
|
||||||
|
|
||||||
|
--- import-from-package-renamed-dynamic ---
|
||||||
|
#import "@test/" + "adder:0.1.0" as adder
|
||||||
|
#test(adder.add(2, 8), 10)
|
||||||
|
|
||||||
--- import-from-package-items ---
|
--- import-from-package-items ---
|
||||||
// Test import with items.
|
// Test import with items.
|
||||||
#import "@test/adder:0.1.0": add
|
#import "@test/adder:0.1.0": add
|
||||||
|
1
tests/suite/scripting/modules/with space.typ
Normal file
1
tests/suite/scripting/modules/with space.typ
Normal file
@ -0,0 +1 @@
|
|||||||
|
// SKIP
|
Loading…
x
Reference in New Issue
Block a user