difftreelog
refactor(ir) flatten obj member
in: master
7 files changed
crates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth1use std::rc::Rc;2use std::{any::Any, cell::RefCell, future::Future};34use jrsonnet_gcmodule::Acyclic;5use jrsonnet_parser::{6 ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, FieldName,7 ForSpecData, IfElse, IfSpecData, ImportKind, Member, ObjBody, Param, ParamsDesc,8 ParserSettings, Slice, SliceDesc, Source, SourcePath, Spanned,9};10use rustc_hash::FxHashMap;1112use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};1314pub struct Import {15 path: ResolvePathOwned,16 expression: bool,17}1819pub struct FoundImports(Vec<Import>);2021// Visits all nodes, trying to find import statements22#[allow(clippy::too_many_lines)]23pub fn find_imports(expr: &Spanned<Expr>, out: &mut FoundImports) {24 fn in_destruct(dest: &Destruct, #[allow(unused_variables)] out: &mut FoundImports) {25 match dest {26 #[cfg(feature = "exp-destruct")]27 Destruct::Array {28 start,29 rest: _,30 end,31 } => {32 for dest in start {33 in_destruct(dest, out);34 }35 for dest in end {36 in_destruct(dest, out);37 }38 }39 #[cfg(feature = "exp-destruct")]40 Destruct::Object { fields, rest: _ } => {41 for (_, dest, default) in fields {42 if let Some(dest) = dest {43 in_destruct(dest, out);44 }45 if let Some(expr) = default {46 find_imports(expr, out);47 }48 }49 }50 #[cfg(feature = "exp-destruct")]51 Destruct::Skip => {}52 Destruct::Full(_) => {}53 }54 }55 fn in_compspec(specs: &[CompSpec], out: &mut FoundImports) {56 for spec in specs {57 match spec {58 CompSpec::IfSpec(IfSpecData(expr)) => find_imports(expr, out),59 CompSpec::ForSpec(ForSpecData(destruct, expr)) => {60 in_destruct(destruct, out);61 find_imports(expr, out);62 }63 }64 }65 }66 fn in_params(params: &ParamsDesc, out: &mut FoundImports) {67 for Param(dest, default) in &*params.0 {68 in_destruct(dest, out);69 if let Some(expr) = default {70 find_imports(expr, out);71 }72 }73 }74 fn in_bind(specs: &[BindSpec], out: &mut FoundImports) {75 for spec in specs {76 match spec {77 BindSpec::Field {78 into: dest,79 value: expr,80 } => {81 in_destruct(dest, out);82 find_imports(expr, out);83 }84 BindSpec::Function {85 name: _,86 params,87 value: expr,88 } => {89 in_params(params, out);90 find_imports(expr, out);91 }92 }93 }94 }95 fn in_args(ArgsDesc { unnamed, named }: &ArgsDesc, out: &mut FoundImports) {96 for expr in unnamed {97 find_imports(expr, out);98 }99 for (_, expr) in named {100 find_imports(expr, out);101 }102 }103 fn in_obj(obj: &ObjBody, out: &mut FoundImports) {104 match obj {105 ObjBody::MemberList(v) => {106 for member in v {107 match member {108 Member::Field(FieldMember {109 name,110 params,111 value,112 ..113 }) => {114 match name {115 FieldName::Fixed(_) => {}116 FieldName::Dyn(expr) => find_imports(expr, out),117 }118 if let Some(params) = params {119 in_params(params, out);120 }121 find_imports(value, out);122 }123 Member::BindStmt(_) => todo!(),124 Member::AssertStmt(assert) => {125 find_imports(&assert.0, out);126 if let Some(expr) = &assert.1 {127 find_imports(expr, out);128 }129 }130 }131 }132 }133 ObjBody::ObjComp(_) => todo!(),134 }135 }136 match &**expr {137 Expr::Import(_, v) => {138 if let Expr::Str(s) = &***v {139 out.0.push(Import {140 path: ResolvePathOwned::Str(s.to_string()),141 expression: matches!(&**expr, Expr::Import(ImportKind::Normal, _)),142 });143 }144 // Non-string import will fail in runtime145 }146147 Expr::Literal(_) | Expr::Str(_) | Expr::Num(_) | Expr::Var(_) => {}148149 Expr::Arr(arr) => {150 for expr in &**arr {151 find_imports(expr, out);152 }153 }154 Expr::ArrComp(expr, specs) => {155 find_imports(expr, out);156 in_compspec(specs, out);157 }158 Expr::Obj(obj) => in_obj(obj, out),159 Expr::ObjExtend(expr, obj) => {160 find_imports(expr, out);161 in_obj(obj, out);162 }163 Expr::BinaryOp(binop) => {164 find_imports(&binop.lhs, out);165 find_imports(&binop.rhs, out);166 }167 Expr::AssertExpr(assert) => {168 let AssertExpr {169 assert: AssertStmt(expr, expr2),170 rest,171 } = &**assert;172 find_imports(expr, out);173 if let Some(expr) = expr2 {174 find_imports(expr, out);175 }176 find_imports(rest, out);177 }178 Expr::LocalExpr(specs, expr) => {179 in_bind(specs, out);180 find_imports(expr, out);181 }182 Expr::Apply(expr, args, _) => {183 find_imports(expr, out);184 in_args(args, out);185 }186 Expr::Index { indexable, parts } => {187 find_imports(indexable, out);188 for part in parts {189 find_imports(&part.value, out);190 }191 }192 Expr::Function(params, expr) => {193 in_params(params, out);194 find_imports(expr, out);195 }196 Expr::IfElse(if_else) => {197 let IfElse {198 cond: IfSpecData(expr),199 cond_then,200 cond_else,201 } = &**if_else;202 find_imports(expr, out);203 find_imports(cond_then, out);204 if let Some(expr) = cond_else {205 find_imports(expr, out);206 }207 }208 Expr::Slice(slice) => {209 let Slice {210 value,211 slice: SliceDesc { start, end, step },212 } = &**slice;213 find_imports(value, out);214 if let Some(expr) = start {215 find_imports(expr, out);216 }217 if let Some(expr) = end {218 find_imports(expr, out);219 }220 if let Some(expr) = step {221 find_imports(expr, out);222 }223 }224 Expr::UnaryOp(_, expr) | Expr::ErrorStmt(expr) => {225 find_imports(expr, out);226 }227 }228}229230pub trait AsyncImportResolver {231 type Error;232 /// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond233 /// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`234 /// where `${vendor}` is a library path.235 ///236 /// `from` should only be returned from [`ImportResolver::resolve`],237 /// or from other defined file, any other value may result in panic238 fn resolve_from(239 &self,240 from: &SourcePath,241 path: &dyn AsPathLike,242 ) -> impl Future<Output = Result<SourcePath, Self::Error>>;243 fn resolve_from_default(244 &self,245 path: &dyn AsPathLike,246 ) -> impl Future<Output = Result<SourcePath, Self::Error>> {247 async { self.resolve_from(&SourcePath::default(), path).await }248 }249250 /// Load resolved file251 /// This should only be called with value returned252 /// from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],253 /// this cannot be resolved using associated type,254 /// as the evaluator uses object instead of generic for [`ImportResolver`]255 fn load_file_contents(256 &self,257 resolved: &SourcePath,258 ) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;259}260261#[derive(Acyclic)]262struct ResolvedImportResolver {263 resolved: RefCell<FxHashMap<(SourcePath, ResolvePathOwned), (SourcePath, bool)>>,264}265impl ImportResolver for ResolvedImportResolver {266 fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {267 unreachable!("all files should be loaded at this point");268 }269270 fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> crate::Result<SourcePath> {271 Ok(self272 .resolved273 .borrow()274 .get(&(from.clone(), path.as_path().to_owned()))275 .expect("all imports should be resolved at this point")276 .0277 .clone())278 }279280 fn resolve_from_default(&self, path: &dyn AsPathLike) -> crate::Result<SourcePath> {281 self.resolve_from(&SourcePath::default(), path)282 }283}284285enum Job {286 LoadFile { path: SourcePath, parse: bool },287 ParseFile(SourcePath),288 ResolveImport { from: SourcePath, import: Import },289}290291#[allow(clippy::future_not_send)]292pub async fn async_import<H>(s: State, handler: H, path: &dyn AsPathLike) -> Result<(), H::Error>293where294 H: AsyncImportResolver,295{296 let resolved = (s.import_resolver() as &dyn Any)297 .downcast_ref::<ResolvedImportResolver>()298 .expect("for async imports, import_resolver should be set to ResolvedImportResolver");299300 let mut resolved_map = resolved.resolved.borrow_mut();301302 let mut queue = vec![Job::LoadFile {303 path: handler.resolve_from_default(path).await?,304 parse: true,305 }];306 while let Some(job) = queue.pop() {307 match job {308 Job::LoadFile { path, parse } => {309 if !s.0.file_cache.borrow().contains_key(&path) {310 let data = handler.load_file_contents(&path).await?;311 s.0.file_cache312 .borrow_mut()313 .insert(path.clone(), FileData::new_bytes(data.as_slice().into()));314 }315 if parse {316 queue.push(Job::ParseFile(path));317 }318 }319 Job::ParseFile(path) => {320 if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {321 if file.parsed.is_none() {322 let Some(code) = file.get_string() else {323 continue;324 };325 let source = Source::new(path.clone(), code.clone());326 // If failed - then skip import327 file.parsed = jrsonnet_parser::parse(&code, &ParserSettings { source })328 .map(Rc::new)329 .ok();330 if let Some(parsed) = &file.parsed {331 let mut imports = FoundImports(vec![]);332 find_imports(parsed, &mut imports);333 for import in imports.0 {334 queue.push(Job::ResolveImport {335 from: path.clone(),336 import,337 });338 }339 }340 }341 }342 }343 Job::ResolveImport { from, import } => {344 if let Some((resolved, expression)) =345 resolved_map.get_mut(&(from.clone(), import.path.clone()))346 {347 if import.expression && !*expression {348 *expression = true;349 queue.push(Job::ParseFile(resolved.clone()));350 }351 continue;352 }353 let resolved = handler.resolve_from(&from, &import.path).await?;354 queue.push(Job::LoadFile {355 path: resolved,356 parse: import.expression,357 });358 }359 }360 }361 Ok(())362}crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -4,7 +4,7 @@
use jrsonnet_interner::IStr;
use jrsonnet_parser::{
ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember, FieldName,
- ForSpecData, IfSpecData, ImportKind, LiteralType, Member, ObjBody, ParamsDesc, Spanned,
+ ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, ParamsDesc, Spanned,
};
use jrsonnet_types::ValType;
use rustc_hash::FxHashMap;
@@ -282,48 +282,38 @@
}
#[allow(clippy::too_many_lines)]
-pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {
+pub fn evaluate_member_list_object(ctx: Context, members: &ObjMembers) -> Result<ObjValue> {
let mut builder = ObjValueBuilder::new();
- let locals = Rc::new(
- members
- .iter()
- .filter_map(|m| match m {
- Member::BindStmt(bind) => Some(bind.clone()),
- _ => None,
- })
- .collect::<Vec<_>>(),
- );
+ let locals = members.locals.clone();
// We have single context for all fields, so we can cache binds
let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));
- for member in members {
- match member {
- Member::Field(field) => {
- evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;
- }
- Member::AssertStmt(stmt) => {
- #[derive(Trace)]
- struct ObjectAssert<B: Trace> {
- uctx: B,
- assert: Rc<AssertStmt>,
+ for field in &members.fields {
+ evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?;
+ }
+
+ if !members.asserts.is_empty() {
+ #[derive(Trace)]
+ struct ObjectAssert<B: Trace> {
+ uctx: B,
+ asserts: Rc<Vec<AssertStmt>>,
+ }
+ impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {
+ fn run(&self, sup_this: SupThis) -> Result<()> {
+ let ctx = self.uctx.bind(sup_this)?;
+ for assert in &*self.asserts {
+ evaluate_assert(ctx.clone(), &assert)?;
}
- impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {
- fn run(&self, sup_this: SupThis) -> Result<()> {
- let ctx = self.uctx.bind(sup_this)?;
- evaluate_assert(ctx, &self.assert)
- }
- }
- builder.assert(ObjectAssert {
- uctx: uctx.clone(),
- assert: stmt.clone(),
- });
- }
- Member::BindStmt(_) => {
- // Already handled
+ Ok(())
}
}
+ builder.assert(ObjectAssert {
+ uctx: uctx.clone(),
+ asserts: members.asserts.clone(),
+ });
}
+
Ok(builder.build())
}
@@ -332,13 +322,7 @@
ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,
ObjBody::ObjComp(obj) => {
let mut builder = ObjValueBuilder::new();
- let locals = Rc::new(
- obj.pre_locals
- .iter()
- .chain(obj.post_locals.iter())
- .cloned()
- .collect::<Vec<_>>(),
- );
+ let locals = obj.locals.clone();
evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {
let uctx = evaluate_object_locals(ctx.clone(), locals.clone());
crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -47,10 +47,10 @@
}
#[derive(Debug, PartialEq, Acyclic)]
-pub enum Member {
+pub(crate) enum Member {
Field(FieldMember),
BindStmt(BindSpec),
- AssertStmt(Rc<AssertStmt>),
+ AssertStmt(AssertStmt),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
@@ -240,7 +240,7 @@
}
}
-#[derive(Debug, Clone, PartialEq, Acyclic)]
+#[derive(Debug, PartialEq, Acyclic)]
pub enum BindSpec {
Field {
into: Destruct,
@@ -275,15 +275,21 @@
#[derive(Debug, PartialEq, Acyclic)]
pub struct ObjComp {
- pub pre_locals: Vec<BindSpec>,
+ pub locals: Rc<Vec<BindSpec>>,
pub field: Rc<FieldMember>,
- pub post_locals: Vec<BindSpec>,
pub compspecs: Vec<CompSpec>,
}
#[derive(Debug, PartialEq, Acyclic)]
+pub struct ObjMembers {
+ pub locals: Rc<Vec<BindSpec>>,
+ pub asserts: Rc<Vec<AssertStmt>>,
+ pub fields: Vec<FieldMember>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
pub enum ObjBody {
- MemberList(Vec<Member>),
+ MemberList(ObjMembers),
ObjComp(ObjComp),
}
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -139,8 +139,8 @@
/ obj:destruct_object(s) {obj}
pub rule bind(s: &ParserSettings) -> expr::BindSpec
- = into:destruct(s) _ "=" _ expr:expr(s) {expr::BindSpec::Field{into, value: Rc::new(expr)}}
- / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec::Function{name, params, value: Rc::new(expr)}}
+ = into:destruct(s) _ "=" _ value:expr(s) {expr::BindSpec::Field{into, value: Rc::new(value)}}
+ / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {expr::BindSpec::Function{name, params, value: Rc::new(value)}}
pub rule assertion(s: &ParserSettings) -> expr::AssertStmt
= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }
@@ -207,20 +207,35 @@
= keyword("local") _ bind:bind(s) {bind}
pub rule member(s: &ParserSettings) -> expr::Member
= bind:obj_local(s) {expr::Member::BindStmt(bind)}
- / assertion:assertion(s) {expr::Member::AssertStmt(Rc::new(assertion))}
+ / assertion:assertion(s) {expr::Member::AssertStmt(assertion)}
/ field:field(s) {expr::Member::Field(field)}
pub rule objinside(s: &ParserSettings) -> expr::ObjBody
= pre_locals:(b: obj_local(s) comma() {b})* &"[" field:field(s) post_locals:(comma() b:obj_local(s) {b})* _ ("," _)? forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {
let mut compspecs = vec![CompSpec::ForSpec(forspec)];
compspecs.extend(others.unwrap_or_default());
+ let mut locals = pre_locals;
+ locals.extend(post_locals);
expr::ObjBody::ObjComp(expr::ObjComp{
- pre_locals,
+ locals: Rc::new(locals),
field: Rc::new(field),
- post_locals,
compspecs,
})
}
- / members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}
+ / members:(member(s) ** comma()) comma()? {
+ let mut locals = Vec::new();
+ let mut asserts = Vec::new();
+ let mut fields = Vec::new();
+ for member in members {
+ match member {
+ Member::Field(field_member) => fields.push(field_member),
+ Member::BindStmt(bind_spec) => locals.push(bind_spec),
+ Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt),
+ }
+ }
+ expr::ObjBody::MemberList(ObjMembers {
+ locals: Rc::new(locals), asserts: Rc::new(asserts), fields
+ })
+ }
pub rule ifspec(s: &ParserSettings) -> IfSpecData
= keyword("if") _ expr:expr(s) {IfSpecData(expr)}
pub rule forspec(s: &ParserSettings) -> ForSpecData
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snap
@@ -7,12 +7,16 @@
lhs: ObjExtend(
Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:0-2,
MemberList(
- [
- BindStmt(
+ ObjMembers {
+ locals: [
Field {
into: Full(
"x",
@@ -21,8 +25,9 @@
1.0,
) from virtual:<test>:15-16,
},
- ),
- Field(
+ ],
+ asserts: [],
+ fields: [
FieldMember {
name: Fixed(
"x",
@@ -34,14 +39,18 @@
"x",
) from virtual:<test>:21-22,
},
- ),
- ],
+ ],
+ },
),
) from virtual:<test>:0-24,
op: Add,
rhs: Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:27-29,
},
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snap
@@ -4,6 +4,10 @@
---
Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:0-2
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snap
@@ -4,8 +4,10 @@
---
Obj(
MemberList(
- [
- Field(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [
FieldMember {
name: Fixed(
"a",
@@ -17,7 +19,7 @@
1.0,
) from virtual:<test>:3-4,
},
- ),
- ],
+ ],
+ },
),
) from virtual:<test>:0-5