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

difftreelog

source

crates/jrsonnet-evaluator/src/async_import.rs9.3 KiBsourcehistory
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};10#[cfg(feature = "ir-parser")]11use jrsonnet_ir_parser::ParserSettings;12#[cfg(not(feature = "ir-parser"))]13use jrsonnet_peg_parser::ParserSettings;14use rustc_hash::FxHashMap;1516use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};1718pub struct Import {19	path: ResolvePathOwned,20	expression: bool,21}2223pub struct FoundImports(Vec<Import>);2425// Visits all nodes, trying to find import statements26#[allow(clippy::too_many_lines)]27pub fn find_imports(expr: &Spanned<Expr>, out: &mut FoundImports) {28	#[allow(unused_variables, clippy::needless_pass_by_ref_mut)]29	fn in_destruct(dest: &Destruct, out: &mut FoundImports) {30		match dest {31			#[cfg(feature = "exp-destruct")]32			Destruct::Array {33				start,34				rest: _,35				end,36			} => {37				for dest in start {38					in_destruct(dest, out);39				}40				for dest in end {41					in_destruct(dest, out);42				}43			}44			#[cfg(feature = "exp-destruct")]45			Destruct::Object { fields, rest: _ } => {46				for (_, dest, default) in fields {47					if let Some(dest) = dest {48						in_destruct(dest, out);49					}50					if let Some(expr) = default {51						find_imports(expr, out);52					}53				}54			}55			#[cfg(feature = "exp-destruct")]56			Destruct::Skip => {}57			Destruct::Full(_) => {}58		}59	}60	fn in_compspec(specs: &[CompSpec], out: &mut FoundImports) {61		for spec in specs {62			match spec {63				CompSpec::IfSpec(IfSpecData(expr)) => find_imports(expr, out),64				CompSpec::ForSpec(ForSpecData(destruct, expr)) => {65					in_destruct(destruct, out);66					find_imports(expr, out);67				}68			}69		}70	}71	fn in_params(params: &ExprParams, out: &mut FoundImports) {72		for ExprParam { destruct, default } in &*params.exprs {73			in_destruct(destruct, out);74			if let Some(expr) = default {75				find_imports(expr, out);76			}77		}78	}79	fn in_bind(specs: &[BindSpec], out: &mut FoundImports) {80		for spec in specs {81			match spec {82				BindSpec::Field {83					into: dest,84					value: expr,85				} => {86					in_destruct(dest, out);87					find_imports(expr, out);88				}89				BindSpec::Function {90					name: _,91					params,92					value: expr,93				} => {94					in_params(params, out);95					find_imports(expr, out);96				}97			}98		}99	}100	fn in_args(ArgsDesc { unnamed, named }: &ArgsDesc, out: &mut FoundImports) {101		for expr in unnamed {102			find_imports(expr, out);103		}104		for (_, expr) in named {105			find_imports(expr, out);106		}107	}108	fn in_obj(obj: &ObjBody, out: &mut FoundImports) {109		match obj {110			ObjBody::MemberList(obj) => {111				for FieldMember {112					name,113					params,114					value,115					..116				} in &obj.fields117				{118					match name {119						FieldName::Fixed(_) => {}120						FieldName::Dyn(expr) => find_imports(expr, out),121					}122					if let Some(params) = params {123						in_params(params, out);124					}125					find_imports(value, out);126				}127				for _ in &*obj.locals {128					todo!()129				}130				for assert in &*obj.asserts {131					find_imports(&assert.0, out);132					if let Some(expr) = &assert.1 {133						find_imports(expr, out);134					}135				}136			}137			ObjBody::ObjComp(_) => todo!(),138		}139	}140	match &**expr {141		Expr::Import(_, v) => {142			if let Expr::Str(s) = &***v {143				out.0.push(Import {144					path: ResolvePathOwned::Str(s.to_string()),145					expression: todo!(),146				});147			}148			// Non-string import will fail in runtime149		}150151		Expr::Literal(_) | Expr::Str(_) | Expr::Num(_) | Expr::Var(_) => {}152153		Expr::Arr(arr) => {154			for expr in &**arr {155				find_imports(expr, out);156			}157		}158		Expr::ArrComp(expr, specs) => {159			find_imports(expr, out);160			in_compspec(specs, out);161		}162		Expr::Obj(obj) => in_obj(obj, out),163		Expr::ObjExtend(expr, obj) => {164			find_imports(expr, out);165			in_obj(obj, out);166		}167		Expr::BinaryOp(binop) => {168			find_imports(&binop.lhs, out);169			find_imports(&binop.rhs, out);170		}171		Expr::AssertExpr(assert) => {172			let AssertExpr {173				assert: AssertStmt(expr, expr2),174				rest,175			} = &**assert;176			find_imports(expr, out);177			if let Some(expr) = expr2 {178				find_imports(expr, out);179			}180			find_imports(rest, out);181		}182		Expr::LocalExpr(specs, expr) => {183			in_bind(specs, out);184			find_imports(expr, out);185		}186		Expr::Apply(expr, args, _) => {187			find_imports(expr, out);188			in_args(args, out);189		}190		Expr::Index { indexable, parts } => {191			find_imports(indexable, out);192			for part in parts {193				find_imports(&part.value, out);194			}195		}196		Expr::Function(params, expr) => {197			in_params(params, out);198			find_imports(expr, out);199		}200		Expr::IfElse(if_else) => {201			let IfElse {202				cond: IfSpecData(expr),203				cond_then,204				cond_else,205			} = &**if_else;206			find_imports(expr, out);207			find_imports(cond_then, out);208			if let Some(expr) = cond_else {209				find_imports(expr, out);210			}211		}212		Expr::Slice(slice) => {213			let Slice {214				value,215				slice: SliceDesc { start, end, step },216			} = &**slice;217			find_imports(value, out);218			if let Some(expr) = start {219				find_imports(expr, out);220			}221			if let Some(expr) = end {222				find_imports(expr, out);223			}224			if let Some(expr) = step {225				find_imports(expr, out);226			}227		}228		Expr::UnaryOp(_, expr) | Expr::ErrorStmt(expr) => {229			find_imports(expr, out);230		}231	}232}233234pub trait AsyncImportResolver {235	type Error;236	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond237	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`238	/// where `${vendor}` is a library path.239	///240	/// `from` should only be returned from [`ImportResolver::resolve`],241	/// or from other defined file, any other value may result in panic242	fn resolve_from(243		&self,244		from: &SourcePath,245		path: &dyn AsPathLike,246	) -> impl Future<Output = Result<SourcePath, Self::Error>>;247	fn resolve_from_default(248		&self,249		path: &dyn AsPathLike,250	) -> impl Future<Output = Result<SourcePath, Self::Error>> {251		async { self.resolve_from(&SourcePath::default(), path).await }252	}253254	/// Load resolved file255	/// This should only be called with value returned256	/// from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],257	/// this cannot be resolved using associated type,258	/// as the evaluator uses object instead of generic for [`ImportResolver`]259	fn load_file_contents(260		&self,261		resolved: &SourcePath,262	) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;263}264265#[derive(Acyclic)]266struct ResolvedImportResolver {267	resolved: RefCell<FxHashMap<(SourcePath, ResolvePathOwned), (SourcePath, bool)>>,268}269impl ImportResolver for ResolvedImportResolver {270	fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {271		unreachable!("all files should be loaded at this point");272	}273274	fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> crate::Result<SourcePath> {275		Ok(self276			.resolved277			.borrow()278			.get(&(from.clone(), path.as_path().to_owned()))279			.expect("all imports should be resolved at this point")280			.0281			.clone())282	}283284	fn resolve_from_default(&self, path: &dyn AsPathLike) -> crate::Result<SourcePath> {285		self.resolve_from(&SourcePath::default(), path)286	}287}288289enum Job {290	LoadFile { path: SourcePath, parse: bool },291	ParseFile(SourcePath),292	ResolveImport { from: SourcePath, import: Import },293}294295#[allow(clippy::future_not_send)]296pub async fn async_import<H>(s: State, handler: H, path: &dyn AsPathLike) -> Result<(), H::Error>297where298	H: AsyncImportResolver,299{300	let resolved = (s.import_resolver() as &dyn Any)301		.downcast_ref::<ResolvedImportResolver>()302		.expect("for async imports, import_resolver should be set to ResolvedImportResolver");303304	let mut queue = vec![Job::LoadFile {305		path: handler.resolve_from_default(path).await?,306		parse: true,307	}];308	while let Some(job) = queue.pop() {309		match job {310			Job::LoadFile { path, parse } => {311				if !s.0.file_cache.borrow().contains_key(&path) {312					let data = handler.load_file_contents(&path).await?;313					s.0.file_cache314						.borrow_mut()315						.insert(path.clone(), FileData::new_bytes(data.as_slice().into()));316				}317				if parse {318					queue.push(Job::ParseFile(path));319				}320			}321			Job::ParseFile(path) => {322				if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {323					if file.parsed.is_none() {324						let Some(code) = file.get_string() else {325							continue;326						};327						let source = Source::new(path.clone(), code.clone());328						// If failed - then skip import329						file.parsed = crate::parse_jsonnet(&code, &ParserSettings { source })330							.map(Rc::new)331							.ok();332						if let Some(parsed) = &file.parsed {333							let mut imports = FoundImports(vec![]);334							find_imports(parsed, &mut imports);335							for import in imports.0 {336								queue.push(Job::ResolveImport {337									from: path.clone(),338									import,339								});340							}341						}342					}343				}344			}345			Job::ResolveImport { from, import } => {346				{347					let mut resolved_map = resolved.resolved.borrow_mut();348					if let Some((resolved, expression)) =349						resolved_map.get_mut(&(from.clone(), import.path.clone()))350					{351						if import.expression && !*expression {352							*expression = true;353							queue.push(Job::ParseFile(resolved.clone()));354						}355						continue;356					}357				}358				let resolved = handler.resolve_from(&from, &import.path).await?;359				queue.push(Job::LoadFile {360					path: resolved,361					parse: import.expression,362				});363			}364		}365	}366	Ok(())367}