1use comemo::TrackedMut;
2use ecow::{eco_format, eco_vec, EcoString};
3use typst_library::diag::{
4 bail, error, warning, At, FileError, SourceResult, Trace, Tracepoint,
5};
6use typst_library::engine::Engine;
7use typst_library::foundations::{Binding, Content, Module, Value};
8use typst_library::World;
9use typst_syntax::ast::{self, AstNode, BareImportError};
10use typst_syntax::package::{PackageManifest, PackageSpec};
11use typst_syntax::{FileId, Span, VirtualPath};
12
13use crate::{eval, Eval, Vm};
14
15impl Eval for ast::ModuleImport<'_> {
16 type Output = Value;
17
18 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
19 let source_expr = self.source();
20 let source_span = source_expr.span();
21
22 let mut source = source_expr.eval(vm)?;
23 let mut is_str = false;
24
25 match &source {
26 Value::Func(func) => {
27 if func.scope().is_none() {
28 bail!(source_span, "cannot import from user-defined functions");
29 }
30 }
31 Value::Type(_) => {}
32 Value::Module(_) => {}
33 Value::Str(path) => {
34 source = Value::Module(import(&mut vm.engine, path, source_span)?);
35 is_str = true;
36 }
37 v => {
38 bail!(
39 source_span,
40 "expected path, module, function, or type, found {}",
41 v.ty()
42 )
43 }
44 }
45
46 let new_name = self.new_name();
48 if let Some(new_name) = new_name {
49 if let ast::Expr::Ident(ident) = self.source() {
50 if ident.as_str() == new_name.as_str() {
51 vm.engine.sink.warn(warning!(
53 new_name.span(),
54 "unnecessary import rename to same name",
55 ));
56 }
57 }
58
59 vm.define(new_name, source.clone());
61 }
62
63 let scope = source.scope().unwrap();
64 match self.imports() {
65 None => {
66 if new_name.is_none() {
67 match self.bare_name() {
68 Ok(name)
70 if !is_str || matches!(source_expr, ast::Expr::Str(_)) =>
71 {
72 if matches!(source_expr, ast::Expr::Ident(_)) {
73 vm.engine.sink.warn(warning!(
74 source_expr.span(),
75 "this import has no effect",
76 ));
77 }
78 vm.scopes.top.bind(name, Binding::new(source, source_span));
79 }
80 Ok(_) | Err(BareImportError::Dynamic) => bail!(
81 source_span, "dynamic import requires an explicit name";
82 hint: "you can name the import with `as`"
83 ),
84 Err(BareImportError::PathInvalid) => bail!(
85 source_span, "module name would not be a valid identifier";
86 hint: "you can rename the import with `as`",
87 ),
88 Err(BareImportError::PackageInvalid) => unreachable!(),
90 }
91 }
92 }
93 Some(ast::Imports::Wildcard) => {
94 for (var, binding) in scope.iter() {
95 vm.scopes.top.bind(var.clone(), binding.clone());
96 }
97 }
98 Some(ast::Imports::Items(items)) => {
99 let mut errors = eco_vec![];
100 for item in items.iter() {
101 let mut path = item.path().iter().peekable();
102 let mut scope = scope;
103
104 while let Some(component) = &path.next() {
105 let Some(binding) = scope.get(component) else {
106 errors.push(error!(component.span(), "unresolved import"));
107 break;
108 };
109
110 if path.peek().is_some() {
111 let value = binding.read();
114 let Some(submodule) = value.scope() else {
115 let error = if matches!(value, Value::Func(function) if function.scope().is_none())
116 {
117 error!(
118 component.span(),
119 "cannot import from user-defined functions"
120 )
121 } else if !matches!(
122 value,
123 Value::Func(_) | Value::Module(_) | Value::Type(_)
124 ) {
125 error!(
126 component.span(),
127 "expected module, function, or type, found {}",
128 value.ty()
129 )
130 } else {
131 panic!("unexpected nested import failure")
132 };
133 errors.push(error);
134 break;
135 };
136
137 scope = submodule;
139 } else {
140 if let ast::ImportItem::Renamed(renamed_item) = &item {
146 if renamed_item.original_name().as_str()
147 == renamed_item.new_name().as_str()
148 {
149 vm.engine.sink.warn(warning!(
150 renamed_item.new_name().span(),
151 "unnecessary import rename to same name",
152 ));
153 }
154 }
155
156 vm.bind(item.bound_name(), binding.clone());
157 }
158 }
159 }
160 if !errors.is_empty() {
161 return Err(errors);
162 }
163 }
164 }
165
166 Ok(Value::None)
167 }
168}
169
170impl Eval for ast::ModuleInclude<'_> {
171 type Output = Content;
172
173 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
174 let span = self.source().span();
175 let source = self.source().eval(vm)?;
176 let module = match source {
177 Value::Str(path) => import(&mut vm.engine, &path, span)?,
178 Value::Module(module) => module,
179 v => bail!(span, "expected path or module, found {}", v.ty()),
180 };
181 Ok(module.content())
182 }
183}
184
185pub fn import(engine: &mut Engine, from: &str, span: Span) -> SourceResult<Module> {
187 if from.starts_with('@') {
188 let spec = from.parse::<PackageSpec>().at(span)?;
189 import_package(engine, spec, span)
190 } else {
191 let id = span.resolve_path(from).at(span)?;
192 import_file(engine, id, span)
193 }
194}
195
196fn import_file(engine: &mut Engine, id: FileId, span: Span) -> SourceResult<Module> {
199 let source = engine.world.source(id).at(span)?;
201
202 if engine.route.contains(source.id()) {
204 bail!(span, "cyclic import");
205 }
206
207 let point = || Tracepoint::Import;
209 eval(
210 engine.routines,
211 engine.world,
212 engine.traced,
213 TrackedMut::reborrow_mut(&mut engine.sink),
214 engine.route.track(),
215 &source,
216 )
217 .trace(engine.world, point, span)
218}
219
220fn import_package(
222 engine: &mut Engine,
223 spec: PackageSpec,
224 span: Span,
225) -> SourceResult<Module> {
226 let (name, id) = resolve_package(engine, spec, span)?;
227 import_file(engine, id, span).map(|module| module.with_name(name))
228}
229
230fn resolve_package(
232 engine: &mut Engine,
233 spec: PackageSpec,
234 span: Span,
235) -> SourceResult<(EcoString, FileId)> {
236 let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
238 let bytes = engine.world.file(manifest_id).at(span)?;
239 let string = bytes.as_str().map_err(FileError::from).at(span)?;
240 let manifest: PackageManifest = toml::from_str(string)
241 .map_err(|err| eco_format!("package manifest is malformed ({})", err.message()))
242 .at(span)?;
243 manifest.validate(&spec).at(span)?;
244
245 Ok((manifest.package.name, manifest_id.join(&manifest.package.entrypoint)))
247}