git.delta.rocks / jrsonnet / refs/commits / 44f6e2c9e550

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};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>);2122// Visits all nodes, trying to find import statements23#[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			// Non-string import will fail in runtime146		}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	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond234	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`235	/// where `${vendor}` is a library path.236	///237	/// `from` should only be returned from [`ImportResolver::resolve`],238	/// or from other defined file, any other value may result in panic239	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	/// Load resolved file252	/// This should only be called with value returned253	/// from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],254	/// this cannot be resolved using associated type,255	/// as the evaluator uses object instead of generic for [`ImportResolver`]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						// If failed - then skip import326						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}