git.delta.rocks / jrsonnet / refs/commits / fc45c6ca2bef

difftreelog

source

crates/jrsonnet-evaluator/src/async_import.rs9.1 KiBsourcehistory
1use std::{any::Any, cell::RefCell, future::Future};23use jrsonnet_gcmodule::Acyclic;4use jrsonnet_parser::{5	ArgsDesc, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, FieldName, ForSpecData,6	IfSpecData, LocExpr, Member, ObjBody, Param, ParamsDesc, ParserSettings, SliceDesc, Source,7	SourcePath,8};9use rustc_hash::FxHashMap;1011use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};1213pub struct Import {14	path: ResolvePathOwned,15	expression: bool,16}1718pub struct FoundImports(Vec<Import>);1920// Visits all nodes, trying to find import statements21#[allow(clippy::too_many_lines)]22pub fn find_imports(expr: &LocExpr, out: &mut FoundImports) {23	fn in_destruct(dest: &Destruct, #[allow(unused_variables)] out: &mut FoundImports) {24		match dest {25			#[cfg(feature = "exp-destruct")]26			Destruct::Array {27				start,28				rest: _,29				end,30			} => {31				for dest in start {32					in_destruct(dest, out);33				}34				for dest in end {35					in_destruct(dest, out);36				}37			}38			#[cfg(feature = "exp-destruct")]39			Destruct::Object { fields, rest: _ } => {40				for (_, dest, default) in fields {41					if let Some(dest) = dest {42						in_destruct(dest, out);43					}44					if let Some(expr) = default {45						find_imports(expr, out);46					}47				}48			}49			#[cfg(feature = "exp-destruct")]50			Destruct::Skip => {}51			Destruct::Full(_) => {}52		}53	}54	fn in_compspec(specs: &[CompSpec], out: &mut FoundImports) {55		for spec in specs {56			match spec {57				CompSpec::IfSpec(IfSpecData(expr)) => find_imports(expr, out),58				CompSpec::ForSpec(ForSpecData(destruct, expr)) => {59					in_destruct(destruct, out);60					find_imports(expr, out);61				}62			}63		}64	}65	fn in_params(params: &ParamsDesc, out: &mut FoundImports) {66		for Param(dest, default) in &*params.0 {67			in_destruct(dest, out);68			if let Some(expr) = default {69				find_imports(expr, out);70			}71		}72	}73	fn in_bind(specs: &[BindSpec], out: &mut FoundImports) {74		for spec in specs {75			match spec {76				BindSpec::Field {77					into: dest,78					value: expr,79				} => {80					in_destruct(dest, out);81					find_imports(expr, out);82				}83				BindSpec::Function {84					name: _,85					params,86					value: expr,87				} => {88					in_params(params, out);89					find_imports(expr, out);90				}91			}92		}93	}94	fn in_args(ArgsDesc { unnamed, named }: &ArgsDesc, out: &mut FoundImports) {95		for expr in unnamed {96			find_imports(expr, out);97		}98		for (_, expr) in named {99			find_imports(expr, out);100		}101	}102	fn in_obj(obj: &ObjBody, out: &mut FoundImports) {103		match obj {104			ObjBody::MemberList(v) => {105				for member in v {106					match member {107						Member::Field(FieldMember {108							name,109							params,110							value,111							..112						}) => {113							match name {114								FieldName::Fixed(_) => {}115								FieldName::Dyn(expr) => find_imports(expr, out),116							}117							if let Some(params) = params {118								in_params(params, out);119							}120							find_imports(value, out);121						}122						Member::BindStmt(_) => todo!(),123						Member::AssertStmt(AssertStmt(expr, expr2)) => {124							find_imports(expr, out);125							if let Some(expr) = expr2 {126								find_imports(expr, out);127							}128						}129					}130				}131			}132			ObjBody::ObjComp(_) => todo!(),133		}134	}135	match &*expr.expr() {136		Expr::Import(v) | Expr::ImportStr(v) | Expr::ImportBin(v) => {137			if let Expr::Str(s) = &*v.expr() {138				out.0.push(Import {139					path: ResolvePathOwned::Str(s.to_string()),140					expression: matches!(&*expr.expr(), Expr::Import(_)),141				});142			}143			// Non-string import will fail in runtime144		}145146		Expr::Literal(_) | Expr::Str(_) | Expr::Num(_) | Expr::Var(_) => {}147148		Expr::Arr(arr) => {149			for expr in arr {150				find_imports(expr, out);151			}152		}153		Expr::ArrComp(expr, specs) => {154			find_imports(expr, out);155			in_compspec(specs, out);156		}157		Expr::Obj(obj) => in_obj(obj, out),158		Expr::ObjExtend(expr, obj) => {159			find_imports(expr, out);160			in_obj(obj, out);161		}162		Expr::BinaryOp(a, _, b) => {163			find_imports(a, out);164			find_imports(b, out);165		}166		Expr::AssertExpr(AssertStmt(expr, expr2), then) => {167			find_imports(expr, out);168			if let Some(expr) = expr2 {169				find_imports(expr, out);170			}171			find_imports(then, out);172		}173		Expr::LocalExpr(specs, expr) => {174			in_bind(specs, out);175			find_imports(expr, out);176		}177		Expr::Apply(expr, args, _) => {178			find_imports(expr, out);179			in_args(args, out);180		}181		Expr::Index { indexable, parts } => {182			find_imports(indexable, out);183			for part in parts {184				find_imports(&part.value, out);185			}186		}187		Expr::Function(params, expr) => {188			in_params(params, out);189			find_imports(expr, out);190		}191		Expr::IfElse {192			cond: IfSpecData(expr),193			cond_then,194			cond_else,195		} => {196			find_imports(expr, out);197			find_imports(cond_then, out);198			if let Some(expr) = cond_else {199				find_imports(expr, out);200			}201		}202		Expr::Slice(expr, SliceDesc { start, end, step }) => {203			find_imports(expr, out);204			if let Some(expr) = start {205				find_imports(expr, out);206			}207			if let Some(expr) = end {208				find_imports(expr, out);209			}210			if let Some(expr) = step {211				find_imports(expr, out);212			}213		}214		Expr::Parened(expr) | Expr::UnaryOp(_, expr) | Expr::ErrorStmt(expr) => {215			find_imports(expr, out);216		}217	}218}219220pub trait AsyncImportResolver {221	type Error;222	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond223	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`224	/// where `${vendor}` is a library path.225	///226	/// `from` should only be returned from [`ImportResolver::resolve`],227	/// or from other defined file, any other value may result in panic228	fn resolve_from(229		&self,230		from: &SourcePath,231		path: &dyn AsPathLike,232	) -> impl Future<Output = Result<SourcePath, Self::Error>>;233	fn resolve_from_default(234		&self,235		path: &dyn AsPathLike,236	) -> impl Future<Output = Result<SourcePath, Self::Error>> {237		async { self.resolve_from(&SourcePath::default(), path).await }238	}239240	/// Load resolved file241	/// This should only be called with value returned242	/// from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],243	/// this cannot be resolved using associated type,244	/// as the evaluator uses object instead of generic for [`ImportResolver`]245	fn load_file_contents(246		&self,247		resolved: &SourcePath,248	) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;249}250251#[derive(Acyclic)]252struct ResolvedImportResolver {253	resolved: RefCell<FxHashMap<(SourcePath, ResolvePathOwned), (SourcePath, bool)>>,254}255impl ImportResolver for ResolvedImportResolver {256	fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {257		unreachable!("all files should be loaded at this point");258	}259260	fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> crate::Result<SourcePath> {261		Ok(self262			.resolved263			.borrow()264			.get(&(from.clone(), path.as_path().to_owned()))265			.expect("all imports should be resolved at this point")266			.0267			.clone())268	}269270	fn resolve_from_default(&self, path: &dyn AsPathLike) -> crate::Result<SourcePath> {271		self.resolve_from(&SourcePath::default(), path)272	}273}274275enum Job {276	LoadFile { path: SourcePath, parse: bool },277	ParseFile(SourcePath),278	ResolveImport { from: SourcePath, import: Import },279}280281#[allow(clippy::future_not_send)]282pub async fn async_import<H>(s: State, handler: H, path: &dyn AsPathLike) -> Result<(), H::Error>283where284	H: AsyncImportResolver,285{286	let resolved = (s.import_resolver() as &dyn Any)287		.downcast_ref::<ResolvedImportResolver>()288		.expect("for async imports, import_resolver should be set to ResolvedImportResolver");289290	let mut resolved_map = resolved.resolved.borrow_mut();291292	let mut queue = vec![Job::LoadFile {293		path: handler.resolve_from_default(path).await?,294		parse: true,295	}];296	while let Some(job) = queue.pop() {297		match job {298			Job::LoadFile { path, parse } => {299				if !s.0.file_cache.borrow().contains_key(&path) {300					let data = handler.load_file_contents(&path).await?;301					s.0.file_cache302						.borrow_mut()303						.insert(path.clone(), FileData::new_bytes(data.as_slice().into()));304				}305				if parse {306					queue.push(Job::ParseFile(path));307				}308			}309			Job::ParseFile(path) => {310				if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {311					if file.parsed.is_none() {312						let Some(code) = file.get_string() else {313							continue;314						};315						let source = Source::new(path.clone(), code.clone());316						// If failed - then skip import317						file.parsed =318							jrsonnet_parser::parse(&code, &ParserSettings { source }).ok();319						if let Some(parsed) = &file.parsed {320							let mut imports = FoundImports(vec![]);321							find_imports(parsed, &mut imports);322							for import in imports.0 {323								queue.push(Job::ResolveImport {324									from: path.clone(),325									import,326								});327							}328						}329					}330				}331			}332			Job::ResolveImport { from, import } => {333				if let Some((resolved, expression)) =334					resolved_map.get_mut(&(from.clone(), import.path.clone()))335				{336					if import.expression && !*expression {337						*expression = true;338						queue.push(Job::ParseFile(resolved.clone()));339					}340					continue;341				}342				let resolved = handler.resolve_from(&from, &import.path).await?;343				queue.push(Job::LoadFile {344					path: resolved,345					parse: import.expression,346				});347			}348		}349	}350	Ok(())351}