1use comemo::TrackedMut;
2use ecow::{EcoString, eco_format, eco_vec};
3use typst_library::World;
4use typst_library::diag::{
5 At, FileError, SourceResult, Trace, Tracepoint, bail, error, warning,
6};
7use typst_library::engine::Engine;
8use typst_library::foundations::{Binding, Content, Module, PathOrStr, Reflect, Value};
9use typst_syntax::ast::{self, AstNode, BareImportError};
10use typst_syntax::package::{PackageManifest, PackageSpec};
11use typst_syntax::{FileId, RootedPath, Span, VirtualPath, VirtualRoot};
12
13use crate::{Eval, Vm, eval};
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 replaced_source = 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).trace(
35 vm.engine.world,
36 || Tracepoint::Import(path.clone().into()),
37 self.span(),
38 )?);
39 replaced_source = true;
40 }
41 v if RootedPath::castable(v) => {
42 let id = v.clone().cast::<RootedPath>().at(source_span)?.intern();
43 source =
44 Value::Module(import_file(&mut vm.engine, id, source_span).trace(
45 vm.engine.world,
46 || Tracepoint::Import(id.get().vpath().get_with_slash().into()),
47 self.span(),
48 )?);
49 replaced_source = true;
50 }
51 v => {
52 bail!(
53 source_span,
54 "expected path, module, function, or type, found {}",
55 v.ty(),
56 )
57 }
58 }
59
60 let new_name = self.new_name();
62 if let Some(new_name) = new_name {
63 if let ast::Expr::Ident(ident) = self.source()
64 && ident.as_str() == new_name.as_str()
65 {
66 vm.engine.sink.warn(warning!(
68 new_name.span(),
69 "unnecessary import rename to same name",
70 ));
71 }
72
73 vm.define(new_name, source.clone());
75 }
76
77 let scope = source.scope().unwrap();
78 match self.imports() {
79 None => {
80 if new_name.is_none() {
81 match self.bare_name() {
82 Ok(name)
84 if !replaced_source
85 || matches!(source_expr, ast::Expr::Str(_)) =>
86 {
87 if matches!(source_expr, ast::Expr::Ident(_)) {
88 vm.engine.sink.warn(warning!(
89 source_expr.span(),
90 "this import has no effect",
91 ));
92 }
93 vm.scopes.top.bind(name, Binding::new(source, source_span));
94 }
95 Ok(_) | Err(BareImportError::Dynamic) => bail!(
96 source_span, "dynamic import requires an explicit name";
97 hint: "you can name the import with `as`";
98 ),
99 Err(BareImportError::PathInvalid) => bail!(
100 source_span, "module name would not be a valid identifier";
101 hint: "you can rename the import with `as`";
102 ),
103 Err(BareImportError::PackageInvalid) => unreachable!(),
105 }
106 }
107 }
108 Some(ast::Imports::Wildcard) => {
109 for (var, binding) in scope.iter() {
110 vm.scopes.top.bind(var.clone(), binding.clone());
111 }
112 }
113 Some(ast::Imports::Items(items)) => {
114 let mut errors = eco_vec![];
115 for item in items.iter() {
116 let mut path = item.path().iter().peekable();
117 let mut scope = scope;
118
119 while let Some(component) = &path.next() {
120 let Some(binding) = scope.get(component) else {
121 errors.push(error!(component.span(), "unresolved import"));
122 break;
123 };
124
125 if path.peek().is_some() {
126 let value = binding.read();
129 let Some(submodule) = value.scope() else {
130 let error = if matches!(value, Value::Func(function) if function.scope().is_none())
131 {
132 error!(
133 component.span(),
134 "cannot import from user-defined functions",
135 )
136 } else if !matches!(
137 value,
138 Value::Func(_) | Value::Module(_) | Value::Type(_)
139 ) {
140 error!(
141 component.span(),
142 "expected module, function, or type, found {}",
143 value.ty(),
144 )
145 } else {
146 panic!("unexpected nested import failure")
147 };
148 errors.push(error);
149 break;
150 };
151
152 scope = submodule;
154 } else {
155 if let ast::ImportItem::Renamed(renamed_item) = &item
161 && renamed_item.original_name().as_str()
162 == renamed_item.new_name().as_str()
163 {
164 vm.engine.sink.warn(warning!(
165 renamed_item.new_name().span(),
166 "unnecessary import rename to same name",
167 ));
168 }
169
170 vm.bind(item.bound_name(), binding.clone());
171 }
172 }
173 }
174 if !errors.is_empty() {
175 return Err(errors);
176 }
177 }
178 }
179
180 Ok(Value::None)
181 }
182}
183
184impl Eval for ast::ModuleInclude<'_> {
185 type Output = Content;
186
187 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
188 let source_span = self.source().span();
189 let source = self.source().eval(vm)?;
190 let module = match source {
191 Value::Str(path) => import(&mut vm.engine, &path, source_span).trace(
192 vm.engine.world,
193 || Tracepoint::Include(path.clone().into()),
194 self.span(),
195 )?,
196 Value::Module(module) => module,
197 v if RootedPath::castable(&v) => {
198 let id = v.cast::<RootedPath>().at(source_span)?.intern();
199 import_file(&mut vm.engine, id, source_span).trace(
200 vm.engine.world,
201 || Tracepoint::Include(id.get().vpath().get_with_slash().into()),
202 self.span(),
203 )?
204 }
205 v => bail!(source_span, "expected path or module, found {}", v.ty()),
206 };
207 Ok(module.content())
208 }
209}
210
211pub fn import(engine: &mut Engine, from: &str, span: Span) -> SourceResult<Module> {
213 if from.starts_with('@') {
214 let spec = from.parse::<PackageSpec>().at(span)?;
215 import_package(engine, spec, span)
216 } else {
217 let path = PathOrStr::Str(from.into())
218 .resolve_if_some(span.id())
219 .at(span)?
220 .intern();
221 import_file(engine, path, span)
222 }
223}
224
225fn import_file(engine: &mut Engine, id: FileId, span: Span) -> SourceResult<Module> {
228 let source = engine.world.source(id).at(span)?;
230
231 if engine.route.contains(source.id()) {
233 bail!(span, "cyclic import");
234 }
235
236 eval(
238 engine.world,
239 engine.library,
240 engine.traced,
241 TrackedMut::reborrow_mut(&mut engine.sink),
242 engine.route.track(),
243 &source,
244 )
245}
246
247fn import_package(
249 engine: &mut Engine,
250 spec: PackageSpec,
251 span: Span,
252) -> SourceResult<Module> {
253 let (name, id) = resolve_package(engine, spec, span)?;
254 import_file(engine, id, span).map(|module| module.with_name(name))
255}
256
257fn resolve_package(
259 engine: &mut Engine,
260 spec: PackageSpec,
261 span: Span,
262) -> SourceResult<(EcoString, FileId)> {
263 let manifest_id = RootedPath::new(
265 VirtualRoot::Package(spec.clone()),
266 VirtualPath::new("typst.toml").unwrap(),
267 )
268 .intern();
269 let bytes = engine.world.file(manifest_id).at(span)?;
270 let string = bytes.as_str().map_err(FileError::from).at(span)?;
271 let manifest: PackageManifest = toml::from_str(string)
272 .map_err(|err| eco_format!("package manifest is malformed ({})", err.message()))
273 .at(span)?;
274 manifest.validate(&spec).at(span)?;
275
276 Ok((
278 manifest.package.name,
279 PathOrStr::Str(manifest.package.entrypoint.into())
280 .resolve(manifest_id)
281 .at(span)?
282 .intern(),
283 ))
284}