1use std::rc::Rc;2use std::{any::Any, cell::RefCell, future::Future};34use jrsonnet_gcmodule::Acyclic;5use jrsonnet_ir::{6 ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, ExprParam, ExprParams,7 FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Slice, SliceDesc,8 Source, SourcePath, Spanned,9};10use jrsonnet_peg_parser::ParserSettings;11use rustc_hash::FxHashMap;1213use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};1415pub struct Import {16 path: ResolvePathOwned,17 expression: bool,18}1920pub struct FoundImports(Vec<Import>);212223#[allow(clippy::too_many_lines)]24pub fn find_imports(expr: &Spanned<Expr>, out: &mut FoundImports) {25 #[allow(unused_variables, clippy::needless_pass_by_ref_mut)]26 fn in_destruct(dest: &Destruct, out: &mut FoundImports) {27 match dest {28 #[cfg(feature = "exp-destruct")]29 Destruct::Array {30 start,31 rest: _,32 end,33 } => {34 for dest in start {35 in_destruct(dest, out);36 }37 for dest in end {38 in_destruct(dest, out);39 }40 }41 #[cfg(feature = "exp-destruct")]42 Destruct::Object { fields, rest: _ } => {43 for (_, dest, default) in fields {44 if let Some(dest) = dest {45 in_destruct(dest, out);46 }47 if let Some(expr) = default {48 find_imports(expr, out);49 }50 }51 }52 #[cfg(feature = "exp-destruct")]53 Destruct::Skip => {}54 Destruct::Full(_) => {}55 }56 }57 fn in_compspec(specs: &[CompSpec], out: &mut FoundImports) {58 for spec in specs {59 match spec {60 CompSpec::IfSpec(IfSpecData(expr)) => find_imports(expr, out),61 CompSpec::ForSpec(ForSpecData(destruct, expr)) => {62 in_destruct(destruct, out);63 find_imports(expr, out);64 }65 }66 }67 }68 fn in_params(params: &ExprParams, out: &mut FoundImports) {69 for ExprParam { destruct, default } in &*params.exprs {70 in_destruct(destruct, out);71 if let Some(expr) = default {72 find_imports(expr, out);73 }74 }75 }76 fn in_bind(specs: &[BindSpec], out: &mut FoundImports) {77 for spec in specs {78 match spec {79 BindSpec::Field {80 into: dest,81 value: expr,82 } => {83 in_destruct(dest, out);84 find_imports(expr, out);85 }86 BindSpec::Function {87 name: _,88 params,89 value: expr,90 } => {91 in_params(params, out);92 find_imports(expr, out);93 }94 }95 }96 }97 fn in_args(ArgsDesc { unnamed, named }: &ArgsDesc, out: &mut FoundImports) {98 for expr in unnamed {99 find_imports(expr, out);100 }101 for (_, expr) in named {102 find_imports(expr, out);103 }104 }105 fn in_obj(obj: &ObjBody, out: &mut FoundImports) {106 match obj {107 ObjBody::MemberList(obj) => {108 for FieldMember {109 name,110 params,111 value,112 ..113 } in &obj.fields114 {115 match name {116 FieldName::Fixed(_) => {}117 FieldName::Dyn(expr) => find_imports(expr, out),118 }119 if let Some(params) = params {120 in_params(params, out);121 }122 find_imports(value, out);123 }124 for _ in &*obj.locals {125 todo!()126 }127 for assert in &*obj.asserts {128 find_imports(&assert.0, out);129 if let Some(expr) = &assert.1 {130 find_imports(expr, out);131 }132 }133 }134 ObjBody::ObjComp(_) => todo!(),135 }136 }137 match &**expr {138 Expr::Import(_, v) => {139 if let Expr::Str(s) = &***v {140 out.0.push(Import {141 path: ResolvePathOwned::Str(s.to_string()),142 expression: matches!(&**expr, Expr::Import(ImportKind::Normal, _)),143 });144 }145 146 }147148 Expr::Literal(_) | Expr::Str(_) | Expr::Num(_) | Expr::Var(_) => {}149150 Expr::Arr(arr) => {151 for expr in &**arr {152 find_imports(expr, out);153 }154 }155 Expr::ArrComp(expr, specs) => {156 find_imports(expr, out);157 in_compspec(specs, out);158 }159 Expr::Obj(obj) => in_obj(obj, out),160 Expr::ObjExtend(expr, obj) => {161 find_imports(expr, out);162 in_obj(obj, out);163 }164 Expr::BinaryOp(binop) => {165 find_imports(&binop.lhs, out);166 find_imports(&binop.rhs, out);167 }168 Expr::AssertExpr(assert) => {169 let AssertExpr {170 assert: AssertStmt(expr, expr2),171 rest,172 } = &**assert;173 find_imports(expr, out);174 if let Some(expr) = expr2 {175 find_imports(expr, out);176 }177 find_imports(rest, out);178 }179 Expr::LocalExpr(specs, expr) => {180 in_bind(specs, out);181 find_imports(expr, out);182 }183 Expr::Apply(expr, args, _) => {184 find_imports(expr, out);185 in_args(args, out);186 }187 Expr::Index { indexable, parts } => {188 find_imports(indexable, out);189 for part in parts {190 find_imports(&part.value, out);191 }192 }193 Expr::Function(params, expr) => {194 in_params(params, out);195 find_imports(expr, out);196 }197 Expr::IfElse(if_else) => {198 let IfElse {199 cond: IfSpecData(expr),200 cond_then,201 cond_else,202 } = &**if_else;203 find_imports(expr, out);204 find_imports(cond_then, out);205 if let Some(expr) = cond_else {206 find_imports(expr, out);207 }208 }209 Expr::Slice(slice) => {210 let Slice {211 value,212 slice: SliceDesc { start, end, step },213 } = &**slice;214 find_imports(value, out);215 if let Some(expr) = start {216 find_imports(expr, out);217 }218 if let Some(expr) = end {219 find_imports(expr, out);220 }221 if let Some(expr) = step {222 find_imports(expr, out);223 }224 }225 Expr::UnaryOp(_, expr) | Expr::ErrorStmt(expr) => {226 find_imports(expr, out);227 }228 }229}230231pub trait AsyncImportResolver {232 type Error;233 234 235 236 237 238 239 fn resolve_from(240 &self,241 from: &SourcePath,242 path: &dyn AsPathLike,243 ) -> impl Future<Output = Result<SourcePath, Self::Error>>;244 fn resolve_from_default(245 &self,246 path: &dyn AsPathLike,247 ) -> impl Future<Output = Result<SourcePath, Self::Error>> {248 async { self.resolve_from(&SourcePath::default(), path).await }249 }250251 252 253 254 255 256 fn load_file_contents(257 &self,258 resolved: &SourcePath,259 ) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;260}261262#[derive(Acyclic)]263struct ResolvedImportResolver {264 resolved: RefCell<FxHashMap<(SourcePath, ResolvePathOwned), (SourcePath, bool)>>,265}266impl ImportResolver for ResolvedImportResolver {267 fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {268 unreachable!("all files should be loaded at this point");269 }270271 fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> crate::Result<SourcePath> {272 Ok(self273 .resolved274 .borrow()275 .get(&(from.clone(), path.as_path().to_owned()))276 .expect("all imports should be resolved at this point")277 .0278 .clone())279 }280281 fn resolve_from_default(&self, path: &dyn AsPathLike) -> crate::Result<SourcePath> {282 self.resolve_from(&SourcePath::default(), path)283 }284}285286enum Job {287 LoadFile { path: SourcePath, parse: bool },288 ParseFile(SourcePath),289 ResolveImport { from: SourcePath, import: Import },290}291292#[allow(clippy::future_not_send)]293pub async fn async_import<H>(s: State, handler: H, path: &dyn AsPathLike) -> Result<(), H::Error>294where295 H: AsyncImportResolver,296{297 let resolved = (s.import_resolver() as &dyn Any)298 .downcast_ref::<ResolvedImportResolver>()299 .expect("for async imports, import_resolver should be set to ResolvedImportResolver");300301 let mut queue = vec![Job::LoadFile {302 path: handler.resolve_from_default(path).await?,303 parse: true,304 }];305 while let Some(job) = queue.pop() {306 match job {307 Job::LoadFile { path, parse } => {308 if !s.0.file_cache.borrow().contains_key(&path) {309 let data = handler.load_file_contents(&path).await?;310 s.0.file_cache311 .borrow_mut()312 .insert(path.clone(), FileData::new_bytes(data.as_slice().into()));313 }314 if parse {315 queue.push(Job::ParseFile(path));316 }317 }318 Job::ParseFile(path) => {319 if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {320 if file.parsed.is_none() {321 let Some(code) = file.get_string() else {322 continue;323 };324 let source = Source::new(path.clone(), code.clone());325 326 file.parsed = jrsonnet_peg_parser::parse(&code, &ParserSettings { source })327 .map(Rc::new)328 .ok();329 if let Some(parsed) = &file.parsed {330 let mut imports = FoundImports(vec![]);331 find_imports(parsed, &mut imports);332 for import in imports.0 {333 queue.push(Job::ResolveImport {334 from: path.clone(),335 import,336 });337 }338 }339 }340 }341 }342 Job::ResolveImport { from, import } => {343 {344 let mut resolved_map = resolved.resolved.borrow_mut();345 if let Some((resolved, expression)) =346 resolved_map.get_mut(&(from.clone(), import.path.clone()))347 {348 if import.expression && !*expression {349 *expression = true;350 queue.push(Job::ParseFile(resolved.clone()));351 }352 continue;353 }354 }355 let resolved = handler.resolve_from(&from, &import.path).await?;356 queue.push(Job::LoadFile {357 path: resolved,358 parse: import.expression,359 });360 }361 }362 }363 Ok(())364}