git.delta.rocks / jrsonnet / refs/commits / 7c5655fe8943

difftreelog

feat lazily evaluate source field in mergePatch

unzkwlwzYaroslav Bolyukin2026-02-08parent: #46ef62a.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -1183,6 +1183,12 @@
 		let entry = receiver.0.new.this_entries.entry(name);
 		entry.insert_entry(member);
 	}
+	/// Inserts thunk, replacing if it is already defined
+	pub fn thunk(self, value: impl Into<Thunk<Val>>) {
+		let (receiver, name, member) = self.build_member(MaybeUnbound::Bound(value.into()));
+		let entry = receiver.0.new.this_entries.entry(name);
+		entry.insert_entry(member);
+	}
 
 	/// Tries to insert value, returns an error if it was already defined
 	pub fn try_value(self, value: impl Into<Val>) -> Result<()> {
modifiedcrates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-formatter/src/lib.rs
1use std::{any::type_name, rc::Rc};23use children::{children_between, trivia_before};4use dprint_core::formatting::{5	condition_helpers::is_multiple_lines,6	condition_resolvers::true_resolver,7	ir_helpers::{new_line_group, with_indent},8	ConditionResolver, ConditionResolverContext, LineNumber, PrintItemPath, PrintItems,9	PrintOptions, Signal,10};11use hi_doc::{Formatting, SnippetBuilder};12use jrsonnet_rowan_parser::{13	nodes::{14		Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,15		DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,16		Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,17		Trivia, TriviaKind, UnaryOperator, Visibility,18	},19	AstNode, AstToken as _, SyntaxToken,20};2122use crate::{23	children::{trivia_after, Child, EndingComments},24	comments::{format_comments, CommentLocation},25};2627mod children;28mod comments;29#[cfg(test)]30mod tests;3132fn with_indent_eoi(cond: ConditionResolver, o: PrintItems, e: EndingComments) -> PrintItems {33	let end_comments_items = {34		let mut items = PrintItems::new();35		if e.should_start_with_newline {36			p!(&mut items, nl);37		}38		format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items);39		items.into_rc_path()40	};41	let items =42		new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path();4344	let indented = with_indent(pi!(@i; nl items(items.into())));4546	pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into())))47}4849pub trait Printable {50	fn print(&self, out: &mut PrintItems);51}5253macro_rules! pi {54	(@i; $($t:tt)*) => {{55		#[allow(unused_mut)]56		let mut o = dprint_core::formatting::PrintItems::new();57		pi!(@s; o: $($t)*);58		o59	}};60	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{61		$o.push_string($e.to_owned());62		pi!(@s; $o: $($t)*);63	}};64	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{65		$o.push_string($e);66		pi!(@s; $o: $($t)*);67	}};68	(@s; $o:ident: nl $($t:tt)*) => {{69		$o.push_signal(dprint_core::formatting::Signal::NewLine);70		pi!(@s; $o: $($t)*);71	}};72	(@s; $o:ident: sonl $($t:tt)*) => {{73		$o.push_signal(dprint_core::formatting::Signal::SpaceOrNewLine);74		pi!(@s; $o: $($t)*);75	}};76	(@s; $o:ident: tab $($t:tt)*) => {{77		$o.push_signal(dprint_core::formatting::Signal::Tab);78		pi!(@s; $o: $($t)*);79	}};80	(@s; $o:ident: >i $($t:tt)*) => {{81		$o.push_signal(dprint_core::formatting::Signal::StartIndent);82		pi!(@s; $o: $($t)*);83	}};84	(@s; $o:ident: <i $($t:tt)*) => {{85		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);86		pi!(@s; $o: $($t)*);87	}};88	(@s; $o:ident: info($v:expr) $($t:tt)*) => {{89		$o.push_info($v);90		pi!(@s; $o: $($t)*);91	}};92	(@s; $o:ident: ln_anchor($v:expr) $($t:tt)*) => {{93		$o.push_anchor(LineNumberAnchor::new($v));94		pi!(@s; $o: $($t)*);95	}};96	(@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{97		$o.push_condition(dprint_core::formatting::conditions::if_true(98			$s,99			$cond.clone(),100			{101				let mut o = PrintItems::new();102				p!(o, $($i)*);103				o104			},105		));106		pi!(@s; $o: $($t)*);107	}};108	(@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{109		$o.push_condition(dprint_core::formatting::conditions::if_true_or(110			$s,111			$cond.clone(),112			{113				let mut o = PrintItems::new();114				p!(o, $($i)*);115				o116			},117			{118				let mut o = PrintItems::new();119				p!(o, $($e)*);120				o121			},122		));123		pi!(@s; $o: $($t)*);124	}};125	(@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{126		$o.push_condition(dprint_core::formatting::conditions::if_true_or(127			$s,128			$cond.clone(),129			{130				let o = PrintItems::new();131				o132			},133			{134				let mut o = PrintItems::new();135				p!(o, $($e)*);136				o137			},138		));139		pi!(@s; $o: $($t)*);140	}};141	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{142		$expr.print($o);143		pi!(@s; $o: $($t)*);144	}};145	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{146		$o.extend($expr);147		pi!(@s; $o: $($t)*);148	}};149	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{150		if $e {151			pi!(@s; $o: $($then)*);152		}153		pi!(@s; $o: $($t)*);154	}};155	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{156		if $e {157			pi!(@s; $o: $($then)*);158		} else {159			pi!(@s; $o: $($else)*);160		}161		pi!(@s; $o: $($t)*);162	}};163	(@s; $i:ident:) => {}164}165macro_rules! p {166	($o:ident, $($t:tt)*) => {167		pi!(@s; $o: $($t)*)168	};169	(&mut $o:ident, $($t:tt)*) => {170		let om = &mut $o;171		pi!(@s; om: $($t)*)172	};173}174pub(crate) use p;175pub(crate) use pi;176177impl<P> Printable for Option<P>178where179	P: Printable,180{181	fn print(&self, out: &mut PrintItems) {182		if let Some(v) = self {183			v.print(out);184		} else {185			p!(186				out,187				string(format!(188					"/*missing {}*/",189					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")190				),)191			);192		}193	}194}195196impl Printable for SyntaxToken {197	fn print(&self, out: &mut PrintItems) {198		p!(out, string(self.to_string()));199	}200}201202impl Printable for Text {203	fn print(&self, out: &mut PrintItems) {204		p!(out, string(format!("{}", self)));205	}206}207impl Printable for Number {208	fn print(&self, out: &mut PrintItems) {209		p!(out, string(format!("{}", self)));210	}211}212213impl Printable for Name {214	fn print(&self, out: &mut PrintItems) {215		p!(out, { self.ident_lit() });216	}217}218219impl Printable for DestructRest {220	fn print(&self, out: &mut PrintItems) {221		p!(out, str("..."));222		if let Some(name) = self.into() {223			p!(out, { name });224		}225	}226}227228impl Printable for Destruct {229	fn print(&self, out: &mut PrintItems) {230		match self {231			Self::DestructFull(f) => {232				p!(out, { f.name() });233			}234			Self::DestructSkip(_) => p!(out, str("?")),235			Self::DestructArray(a) => {236				p!(out, str("[") >i nl);237				for el in a.destruct_array_parts() {238					match el {239						DestructArrayPart::DestructArrayElement(e) => {240							p!(out, {e.destruct()} str(",") nl);241						}242						DestructArrayPart::DestructRest(d) => {243							p!(out, {d} str(",") nl);244						}245					}246				}247				p!(out, <i str("]"));248			}249			Self::DestructObject(o) => {250				p!(out, str("{") >i nl);251				for item in o.destruct_object_fields() {252					p!(out, { item.field() });253					if let Some(des) = item.destruct() {254						p!(out, str(": ") {des});255					}256					if let Some(def) = item.expr() {257						p!(out, str(" = ") {def});258					}259					p!(out, str(",") nl);260				}261				if let Some(rest) = o.destruct_rest() {262					p!(out, {rest} nl);263				}264				p!(out, <i str("}"));265			}266		}267	}268}269270impl Printable for FieldName {271	fn print(&self, out: &mut PrintItems) {272		match self {273			Self::FieldNameFixed(f) => {274				if let Some(id) = f.id() {275					p!(out, { id });276				} else if let Some(str) = f.text() {277					p!(out, { str });278				} else {279					p!(out, str("/*missing FieldName*/"));280				}281			}282			Self::FieldNameDynamic(d) => {283				p!(out, str("[") {d.expr()} str("]"));284			}285		}286	}287}288289impl Printable for Visibility {290	fn print(&self, out: &mut PrintItems) {291		p!(out, string(self.to_string()));292	}293}294295impl Printable for ObjLocal {296	fn print(&self, out: &mut PrintItems) {297		p!(out, str("local ") {self.bind()});298	}299}300301impl Printable for Assertion {302	fn print(&self, out: &mut PrintItems) {303		p!(out, str("assert ") {self.condition()});304		if self.colon_token().is_some() || self.message().is_some() {305			p!(out, str(": ") {self.message()});306		}307	}308}309310impl Printable for ParamsDesc {311	fn print(&self, out: &mut PrintItems) {312		p!(out, str("(") >i nl);313		for param in self.params() {314			p!(out, { param.destruct() });315			if param.assign_token().is_some() || param.expr().is_some() {316				p!(out, str(" = ") {param.expr()});317			}318			p!(out, str(",") nl);319		}320		p!(out, <i str(")"));321	}322}323impl Printable for ArgsDesc {324	fn print(&self, out: &mut PrintItems) {325		let start = LineNumber::new("args start line");326		let end = LineNumber::new("args end line");327		let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {328			is_multiple_lines(condition_context, start, end)329		});330331		let (children, end_comments) = children_between::<Arg>(332			self.syntax().clone(),333			self.l_paren_token().map(Into::into).as_ref(),334			self.r_paren_token().map(Into::into).as_ref(),335			None,336		);337338		fn gen_args(children: Vec<Child<Arg>>, multi_line: ConditionResolver) -> PrintItems {339			let mut _out = PrintItems::new();340			let out = &mut _out;341342			let mut args = children.into_iter().peekable();343			while let Some(ele) = args.next() {344				if ele.should_start_with_newline {345					p!(out, nl);346				}347				format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);348				let arg = ele.value;349				if arg.name().is_some() || arg.assign_token().is_some() {350					p!(out, {arg.name()} str(" = "));351				}352				p!(out, { arg.expr() });353				let has_more = args.peek().is_some();354				if has_more {355					p!(out, str(","));356				} else {357					p!(out, if("trailing comma", multi_line, str(",")));358				}359				format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);360				if has_more {361					p!(out, if_else("arg separator", multi_line, nl)(sonl));362				}363			}364			_out365		}366367		let args_items = new_line_group(gen_args(children, multi_line.clone())).into_rc_path();368		let args_indented = with_indent(pi!(@i; nl items(args_items.into())));369370		p!(out, str("(") info(start));371		p!(out, if_else("args body", multi_line, items(args_indented) nl)(items(args_items.into())));372		if end_comments.should_start_with_newline {373			p!(out, nl);374		}375		format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);376		p!(out, str(")") info(end));377	}378}379impl Printable for SliceDesc {380	fn print(&self, out: &mut PrintItems) {381		p!(out, str("["));382		if self.from().is_some() {383			p!(out, { self.from() });384		}385		p!(out, str(":"));386		if self.end().is_some() {387			p!(out, { self.end().map(|e| e.expr()) });388		}389		// Keep only one : in case if we don't need step390		if self.step().is_some() {391			p!(out, str(":") {self.step().map(|e|e.expr())});392		}393		p!(out, str("]"));394	}395}396397impl Printable for Member {398	fn print(&self, out: &mut PrintItems) {399		match self {400			Self::MemberBindStmt(b) => {401				p!(out, { b.obj_local() });402			}403			Self::MemberAssertStmt(ass) => {404				p!(out, { ass.assertion() });405			}406			Self::MemberFieldNormal(n) => {407				p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});408			}409			Self::MemberFieldMethod(m) => {410				p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});411			}412		}413	}414}415416impl Printable for ObjBody {417	fn print(&self, out: &mut PrintItems) {418		match self {419			Self::ObjBodyComp(l) => {420				let (children, mut end_comments) = children_between::<Member>(421					l.syntax().clone(),422					l.l_brace_token().map(Into::into).as_ref(),423					Some(424						&(l.comp_specs()425							.next()426							.expect("at least one spec is defined")427							.syntax()428							.clone())429						.into(),430					),431					None,432				);433				let trailing_for_comp = end_comments.extract_trailing();434				p!(out, str("{") >i nl);435				for mem in children {436					if mem.should_start_with_newline {437						p!(out, nl);438					}439					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);440					p!(out, {mem.value} str(","));441					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);442					p!(out, nl);443				}444445				if end_comments.should_start_with_newline {446					p!(out, nl);447				}448				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);449450				let (compspecs, end_comments) = children_between::<CompSpec>(451					l.syntax().clone(),452					l.member_comps()453						.last()454						.map(|m| m.syntax().clone())455						.map(Into::into)456						.or_else(|| l.l_brace_token().map(Into::into))457						.as_ref(),458					l.r_brace_token().map(Into::into).as_ref(),459					Some(trailing_for_comp),460				);461				for mem in compspecs {462					if mem.should_start_with_newline {463						p!(out, nl);464					}465					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);466					p!(out, { mem.value });467					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);468				}469				if end_comments.should_start_with_newline {470					p!(out, nl);471				}472				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);473474				p!(out, nl <i str("}"));475			}476			Self::ObjBodyMemberList(l) => {477				let (children, end_comments) = children_between::<Member>(478					l.syntax().clone(),479					l.l_brace_token().map(Into::into).as_ref(),480					l.r_brace_token().map(Into::into).as_ref(),481					None,482				);483				if children.is_empty() && end_comments.is_empty() {484					p!(out, str("{ }"));485					return;486				}487488				let source_is_multiline = children.iter().any(|c| c.triggers_multiline)489					|| end_comments.should_start_with_newline;490491				let start = LineNumber::new("obj start line");492				let end = LineNumber::new("obj end line");493				let multi_line: ConditionResolver = if source_is_multiline {494					true_resolver()495				} else {496					Rc::new(move |ctx: &mut ConditionResolverContext| {497						is_multiple_lines(ctx, start, end)498					})499				};500501				fn gen_members(502					children: Vec<Child<Member>>,503					multi_line: ConditionResolver,504				) -> PrintItems {505					let mut _out = PrintItems::new();506					let out = &mut _out;507					let mut members = children.into_iter().peekable();508					while let Some(mem) = members.next() {509						if mem.should_start_with_newline {510							p!(out, nl);511						}512						format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);513						p!(out, { mem.value });514						let has_more = members.peek().is_some();515						if has_more {516							p!(out, str(","));517						} else {518							p!(out, if("trailing comma", multi_line, str(",")));519						}520						format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);521						p!(out, if_else("member separator", multi_line, nl)(sonl));522					}523					_out524				}525526				let members_items =527					new_line_group(gen_members(children, multi_line.clone())).into_rc_path();528529				let members = with_indent_eoi(multi_line, members_items.into(), end_comments);530531				p!(out, str("{") info(start));532				p!(out, items(members));533				p!(out, str("}") info(end));534			}535		}536	}537}538impl Printable for UnaryOperator {539	fn print(&self, out: &mut PrintItems) {540		p!(out, string(self.text().to_string()));541	}542}543impl Printable for BinaryOperator {544	fn print(&self, out: &mut PrintItems) {545		p!(out, string(self.text().to_string()));546	}547}548impl Printable for Bind {549	fn print(&self, out: &mut PrintItems) {550		match self {551			Self::BindDestruct(d) => {552				p!(out, {d.into()} str(" = ") {d.value()});553			}554			Self::BindFunction(f) => {555				p!(out, {f.name()} {f.params()} str(" = ") {f.value()});556			}557		}558	}559}560impl Printable for Literal {561	fn print(&self, out: &mut PrintItems) {562		p!(out, string(self.syntax().to_string()));563	}564}565impl Printable for ImportKind {566	fn print(&self, out: &mut PrintItems) {567		p!(out, string(self.syntax().to_string()));568	}569}570impl Printable for ForSpec {571	fn print(&self, out: &mut PrintItems) {572		p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});573	}574}575impl Printable for IfSpec {576	fn print(&self, out: &mut PrintItems) {577		p!(out, str("if ") {self.expr()});578	}579}580impl Printable for CompSpec {581	fn print(&self, out: &mut PrintItems) {582		match self {583			Self::ForSpec(f) => f.print(out),584			Self::IfSpec(i) => i.print(out),585		}586	}587}588impl Printable for Expr {589	fn print(&self, out: &mut PrintItems) {590		let (stmts, _ending) = children_between::<Stmt>(591			self.syntax().clone(),592			None,593			self.expr_base()594				.as_ref()595				.map(ExprBase::syntax)596				.cloned()597				.map(Into::into)598				.as_ref(),599			None,600		);601		for stmt in stmts {602			p!(out, { stmt.value });603		}604		p!(out, { self.expr_base() });605		let (suffixes, _ending) = children_between::<Suffix>(606			self.syntax().clone(),607			self.expr_base()608				.as_ref()609				.map(ExprBase::syntax)610				.cloned()611				.map(Into::into)612				.as_ref(),613			None,614			None,615		);616		for suffix in suffixes {617			p!(out, { suffix.value });618		}619	}620}621impl Printable for Suffix {622	fn print(&self, out: &mut PrintItems) {623		match self {624			Self::SuffixIndex(i) => {625				if i.question_mark_token().is_some() {626					p!(out, str("?"));627				}628				p!(out, str(".") {i.index()});629			}630			Self::SuffixIndexExpr(e) => {631				if e.question_mark_token().is_some() {632					p!(out, str(".?"));633				}634				p!(out, str("[") {e.index()} str("]"));635			}636			Self::SuffixSlice(d) => {637				p!(out, { d.slice_desc() });638			}639			Self::SuffixApply(a) => {640				p!(out, { a.args_desc() });641			}642		}643	}644}645impl Printable for Stmt {646	fn print(&self, out: &mut PrintItems) {647		match self {648			Self::StmtLocal(l) => {649				let (binds, end_comments) = children_between::<Bind>(650					l.syntax().clone(),651					l.local_kw_token().map(Into::into).as_ref(),652					l.semi_token().map(Into::into).as_ref(),653					None,654				);655				if binds.len() == 1 {656					let bind = &binds[0];657					format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);658					p!(out, str("local ") {bind.value});659				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?660				} else {661					p!(out,str("local") >i nl);662					for bind in binds {663						if bind.should_start_with_newline {664							p!(out, nl);665						}666						format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);667						p!(out, {bind.value} str(","));668						format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);669						p!(out, nl);670					}671					if end_comments.should_start_with_newline {672						p!(out, nl);673					}674					format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);675					p!(out,<i);676				}677				p!(out,str(";") nl);678			}679			Self::StmtAssert(a) => {680				p!(out, {a.assertion()} str(";") nl);681			}682		}683	}684}685impl Printable for ExprBase {686	fn print(&self, out: &mut PrintItems) {687		match self {688			Self::ExprBinary(b) => {689				p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()});690			}691			Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),692			// Self::ExprSlice(s) => {693			// 	p!(new: {s.expr()} {s.slice_desc()})694			// }695			// Self::ExprIndex(i) => {696			// 	p!(new: {i.expr()} str(".") {i.index()})697			// }698			// Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),699			// Self::ExprApply(a) => {700			// 	let mut pi = p!(new: {a.expr()} {a.args_desc()});701			// 	if a.tailstrict_kw_token().is_some() {702			// 		p!(out,str(" tailstrict"));703			// 	}704			// 	pi705			// }706			Self::ExprObjExtend(ex) => {707				p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});708			}709			Self::ExprParened(p) => {710				p!(out, str("(") {p.expr()} str(")"));711			}712			Self::ExprString(s) => p!(out, { s.text() }),713			Self::ExprNumber(n) => p!(out, { n.number() }),714			Self::ExprArray(a) => {715				p!(out, str("[") >i nl);716				for el in a.exprs() {717					p!(out, {el} str(",") nl);718				}719				p!(out, <i str("]"));720			}721			Self::ExprObject(obj) => {722				p!(out, { obj.obj_body() });723			}724			Self::ExprArrayComp(arr) => {725				p!(out, str("[") {arr.expr()});726				for spec in arr.comp_specs() {727					p!(out, str(" ") {spec});728				}729				p!(out, str("]"));730			}731			Self::ExprImport(v) => {732				p!(out, {v.import_kind()} str(" ") {v.text()});733			}734			Self::ExprVar(n) => p!(out, { n.name() }),735			// Self::ExprLocal(l) => {736			// }737			Self::ExprIfThenElse(ite) => {738				p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});739				if ite.else_kw_token().is_some() || ite.else_().is_some() {740					p!(out, str(" else ") {ite.else_().map(|t| t.expr())});741				}742			}743			Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),744			// Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),745			Self::ExprError(e) => p!(out, str("error ") {e.expr()}),746			Self::ExprLiteral(l) => {747				p!(out, { l.literal() });748			}749		}750	}751}752753impl Printable for SourceFile {754	fn print(&self, out: &mut PrintItems) {755		let before = trivia_before(756			self.syntax().clone(),757			self.expr()758				.map(|e| e.syntax().clone())759				.map(Into::into)760				.as_ref(),761		);762		let after = trivia_after(763			self.syntax().clone(),764			self.expr()765				.map(|e| e.syntax().clone())766				.map(Into::into)767				.as_ref(),768		);769		format_comments(&before, CommentLocation::AboveItem, out);770		p!(out, {self.expr()} nl);771		format_comments(&after, CommentLocation::EndOfItems, out);772	}773}774775pub struct FormatOptions {776	// 0 for hard tabs777	pub indent: u8,778}779pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {780	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);781	if !errors.is_empty() {782		let mut builder = hi_doc::SnippetBuilder::new(input);783		for error in errors {784			builder785				.error(hi_doc::Text::fragment(786					format!("{:?}", error.error),787					Formatting::default(),788				))789				.range(790					error.range.start().into()791						..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),792				)793				.build();794		}795		// let snippet = builder.build();796		return Err(builder);797		// It is possible to recover from this failure, but the output may be broken, as formatter is free to skip798		// ERROR rowan nodes.799		// Recovery needs to be enabled for LSP, though.800	}801	Ok(dprint_core::formatting::format(802		|| {803			let mut out = PrintItems::new();804			parsed.print(&mut out);805			out806		},807		PrintOptions {808			indent_width: if opts.indent == 0 {809				// Reasonable max length for both 2 and 4 space sized tabs.810				3811			} else {812				opts.indent813			},814			max_width: 100,815			use_tabs: opts.indent == 0,816			new_line_text: "\n",817		},818	))819}
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -201,7 +201,9 @@
 	let mut out = ObjValueBuilder::new();
 	for field in target_fields.union(&patch_fields) {
 		let Some(field_patch) = patch.get(field.clone())? else {
-			out.field(field.clone()).value(target.get(field.clone())?.expect("we're iterating over fields union, if field is missing in patch - it exists in target"));
+			// All lazy fields might be unified into a single filtered object core instead of creating a thunk per, but this implementation is good enough.
+			let target_field = target.get_lazy(field.clone()).expect("we're iterating over fields union, if field is missing in patch - it exists in target");
+			out.field(field.clone()).thunk(target_field);
 			continue;
 		};
 		if matches!(field_patch, Val::Null) {
addedtests/golden/issue188.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue188.jsonnet
@@ -0,0 +1 @@
+std.mergePatch({ val: error 'should not error' }, {}) + { val+:: {} }
addedtests/golden/issue188.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue188.jsonnet.golden
@@ -0,0 +1 @@
+{ }
\ No newline at end of file