git.delta.rocks / jrsonnet / refs/commits / 0b2a41fa24cc

difftreelog

fix(rowan) statements bind the tighest

nmxzmtonYaroslav Bolyukin2026-04-01parent: #6612618.patch.diff
in: master

3 files changed

modifiedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
before · crates/jrsonnet-rowan-parser/src/parser.rs
1use std::{cell::Cell, fmt, rc::Rc};23use rowan::{GreenNode, TextRange};45use crate::{6	event::Event,7	marker::{CompletedMarker, Marker},8	nodes::{BinaryOperatorKind, Literal, Number, Text, UnaryOperatorKind},9	token_set::SyntaxKindSet,10	AstToken, SyntaxKind,11	SyntaxKind::*,12	SyntaxNode, T, TS,13};1415pub struct Parse {16	pub green_node: GreenNode,17	pub errors: Vec<LocatedSyntaxError>,18}1920pub struct Parser {21	// TODO: remove all trivia before feeding to parser?22	kinds: Vec<SyntaxKind>,23	pub offset: usize,24	pub events: Vec<Event>,25	pub entered: u32,26	pub hints: Vec<(u32, TextRange, String)>,27	pub last_error_token: usize,28	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>,29	steps: Cell<u64>,30}3132#[derive(Clone, Debug)]33pub enum SyntaxError {34	Unexpected {35		expected: ExpectedSyntax,36		found: SyntaxKind,37	},38	Missing {39		expected: ExpectedSyntax,40	},41	Custom {42		error: String,43	},44	Hint {45		error: String,46	},47}48impl fmt::Display for SyntaxError {49	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {50		match self {51			SyntaxError::Unexpected { expected, found } => {52				write!(f, "unexpected {found:?}, expecting {expected}")53			}54			SyntaxError::Missing { expected } => write!(f, "missing {expected}"),55			SyntaxError::Custom { error } | SyntaxError::Hint { error } => write!(f, "{error}"),56		}57	}58}5960#[derive(Debug)]61pub struct LocatedSyntaxError {62	pub error: SyntaxError,63	pub range: TextRange,64}6566impl Parser {67	pub fn new(kinds: Vec<SyntaxKind>) -> Self {68		Self {69			kinds,70			offset: 0,71			events: vec![],72			entered: 0,73			last_error_token: 0,74			hints: vec![],75			expected_syntax_tracking_state: Rc::new(Cell::new(ExpectedSyntax::Unnamed(TS![]))),76			steps: Cell::new(0),77		}78	}79	pub fn clear_outdated_hints(&mut self) {80		let amount = self81			.hints82			.iter()83			.rev()84			.take_while(|h| h.0 > self.entered)85			.count();86		self.hints.truncate(self.hints.len() - amount);87	}88	fn clear_expected_syntaxes(&self) {89		self.expected_syntax_tracking_state90			.set(ExpectedSyntax::Unnamed(TS![]));91	}92	pub fn start(&mut self) -> Marker {93		let start_event_idx = self.events.len();94		self.events.push(Event::Pending);95		self.entered += 1;96		Marker::new(start_event_idx)97	}98	// pub fn start_ranger(&mut self) -> Ranger {99	// 	let pos = self.offset;100	// 	Ranger { pos }101	// }102	pub fn parse(mut self) -> Vec<Event> {103		let m = self.start();104		expr(&mut self);105		if !self.at(EOF) {106			let m = self.start();107			while !self.at(EOF) {108				self.bump();109			}110			m.complete_error(&mut self, "unexpected tokens after end");111		}112		m.complete(&mut self, SOURCE_FILE);113114		self.events115	}116117	pub(crate) fn expect(&mut self, kind: SyntaxKind) {118		self.expect_with_recovery_set(kind, TS![]);119	}120121	pub(crate) fn expect_with_recovery_set(122		&mut self,123		kind: SyntaxKind,124		recovery_set: SyntaxKindSet,125	) {126		if self.at(kind) {127			if kind != EOF {128				self.bump();129			}130		} else {131			self.error_with_recovery_set(recovery_set);132		}133	}134135	// pub(crate) fn expect_with_no_skip(&mut self, kind: SyntaxKind) {136	// 	if self.at(kind) {137	// 		self.bump();138	// 	} else {139	// 		self.error_with_no_skip();140	// 	}141	// }142	pub fn error_with_no_skip(&mut self) -> CompletedMarker {143		self.error_with_recovery_set(SyntaxKindSet::ALL)144	}145146	pub fn error_with_recovery_set(&mut self, recovery_set: SyntaxKindSet) -> CompletedMarker {147		let expected = self.expected_syntax_tracking_state.get();148		self.expected_syntax_tracking_state149			.set(ExpectedSyntax::Unnamed(TS![]));150151		if self.at_end() || self.at_ts(recovery_set) {152			let m = self.start();153			return m.complete_missing(self, expected);154		}155156		let current_token = self.current();157158		self.last_error_token = self.offset;159160		let m = self.start();161		self.bump();162		let m = m.complete_unexpected(self, expected, current_token);163		self.clear_expected_syntaxes();164		m165	}166	fn bump_assert(&mut self, kind: SyntaxKind) {167		assert!(self.at(kind), "expected {kind:?}");168		self.bump_remap(self.current());169	}170	fn bump(&mut self) {171		self.bump_remap(self.current());172	}173	fn bump_remap(&mut self, kind: SyntaxKind) {174		assert_ne!(self.offset, self.kinds.len(), "already at end");175		self.events.push(Event::Token { kind });176		self.offset += 1;177		self.clear_expected_syntaxes();178	}179	fn step(&self) {180		use std::fmt::Write;181		let steps = self.steps.get();182		if steps >= 15_000_000 {183			let mut out = "seems like parsing is stuck".to_owned();184			{185				let last = 20;186				write!(out, "\n\nLast {last} events:").unwrap();187				for (i, event) in self188					.events189					.iter()190					.skip(self.events.len().saturating_sub(last))191					.enumerate()192				{193					write!(out, "\n{i}. {event:?}").unwrap();194				}195			}196			{197				let next = 20;198				write!(out, "\n\nNext {next} tokens:").unwrap();199				for (i, tok) in self.kinds.iter().skip(self.offset).take(next).enumerate() {200					write!(out, "\n{i}. {tok:?}").unwrap();201				}202			}203			panic!("{out}")204		}205		self.steps.set(steps + 1);206	}207	fn nth(&self, i: usize) -> SyntaxKind {208		self.step();209		let mut offset = self.offset;210		for _ in 0..i {211			offset += 1;212		}213		self.kinds.get(offset).copied().unwrap_or(EOF)214	}215	fn current(&self) -> SyntaxKind {216		self.nth(0)217	}218	#[must_use]219	pub(crate) fn expected_syntax_name(&self, name: &'static str) -> ExpectedSyntaxGuard {220		self.expected_syntax_tracking_state221			.set(ExpectedSyntax::Named(name));222223		ExpectedSyntaxGuard::new(Rc::clone(&self.expected_syntax_tracking_state))224	}225	pub fn at(&self, kind: SyntaxKind) -> bool {226		self.nth_at(0, kind)227	}228	pub fn nth_at(&self, n: usize, kind: SyntaxKind) -> bool {229		if n == 0 {230			if let ExpectedSyntax::Unnamed(kinds) = self.expected_syntax_tracking_state.get() {231				let kinds = kinds.with(kind);232				self.expected_syntax_tracking_state233					.set(ExpectedSyntax::Unnamed(kinds));234			}235		}236		self.nth(n) == kind237	}238	pub fn at_ts(&self, set: SyntaxKindSet) -> bool {239		if let ExpectedSyntax::Unnamed(kinds) = self.expected_syntax_tracking_state.get() {240			let kinds = kinds.union(set);241			self.expected_syntax_tracking_state242				.set(ExpectedSyntax::Unnamed(kinds));243		}244		set.contains(self.current())245	}246	pub fn at_end(&self) -> bool {247		self.at(EOF)248	}249}250pub struct ExpectedSyntaxGuard {251	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>,252}253254impl ExpectedSyntaxGuard {255	fn new(expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>) -> Self {256		Self {257			expected_syntax_tracking_state,258		}259	}260}261262impl Drop for ExpectedSyntaxGuard {263	fn drop(&mut self) {264		self.expected_syntax_tracking_state265			.set(ExpectedSyntax::Unnamed(TS![]));266	}267}268269#[derive(Clone, Debug, Copy)]270pub enum ExpectedSyntax {271	Named(&'static str),272	Unnamed(SyntaxKindSet),273}274impl fmt::Display for ExpectedSyntax {275	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {276		match self {277			Self::Named(name) => write!(f, "{name}"),278			Self::Unnamed(set) => write!(f, "{set}"),279		}280	}281}282283fn expr(p: &mut Parser) -> CompletedMarker {284	let m = p.start();285	while p.at(T![local]) || p.at(T![assert]) {286		let m = p.start();287288		if p.at(T![local]) {289			p.bump();290			loop {291				if p.at(T![;]) {292					p.bump();293					break;294				}295				bind(p);296297				if p.at(T![,]) {298					p.bump();299					continue;300				}301				p.expect(T![;]);302				break;303			}304			m.complete(p, STMT_LOCAL);305		} else {306			assertion(p);307			p.expect(T![;]);308			m.complete(p, STMT_ASSERT);309		}310	}311	match expr_binding_power(p, 0) {312		Ok(m) | Err(m) => m,313	};314	m.complete(p, EXPR)315}316fn expr_binding_power(317	p: &mut Parser,318	minimum_binding_power: u8,319) -> Result<CompletedMarker, CompletedMarker> {320	let mut lhs = lhs(p)?;321322	while let Some(op) = BinaryOperatorKind::cast(p.current())323		.or_else(|| p.at(T!['{']).then_some(BinaryOperatorKind::MetaObjectApply))324	{325		let (left_binding_power, right_binding_power) = op.binding_power();326		if left_binding_power < minimum_binding_power {327			break;328		}329330		let m = lhs.wrap(p, EXPR, false);331332		// Object apply is not a real operator, we dont have something to bump333		if op != BinaryOperatorKind::MetaObjectApply {334			p.bump();335		}336337		let m = m.precede(p);338		let parsed_rhs = expr_binding_power(p, right_binding_power)339			.map(|v| v.precede(p).complete(p, EXPR))340			.is_ok();341		lhs = m.complete(342			p,343			if op == BinaryOperatorKind::MetaObjectApply {344				EXPR_OBJ_EXTEND345			} else {346				EXPR_BINARY347			},348		);349350		if !parsed_rhs {351			break;352		}353	}354	Ok(lhs)355}356357const COMPSPEC: SyntaxKindSet = TS![for if];358fn compspec(p: &mut Parser) -> CompletedMarker {359	assert!(p.at_ts(COMPSPEC));360	if p.at(T![for]) {361		let m = p.start();362		p.bump();363		destruct(p);364		p.expect(T![in]);365		expr(p);366		m.complete(p, FOR_SPEC)367	} else if p.at(T![if]) {368		let m = p.start();369		p.bump();370		expr(p);371		m.complete(p, IF_SPEC)372	} else {373		unreachable!()374	}375}376377fn comma(p: &mut Parser) -> bool {378	comma_with_alternatives(p, TS![])379}380fn comma_with_alternatives(p: &mut Parser, set: SyntaxKindSet) -> bool {381	if p.at(T![,]) {382		p.bump();383		true384	} else if p.at_ts(set) {385		let _ex = p.expected_syntax_name("comma");386		p.expect_with_recovery_set(T![,], TS![]);387		true388	} else {389		false390	}391}392393fn field_name(p: &mut Parser) {394	let _e = p.expected_syntax_name("field name");395	let m = p.start();396	if p.at(T!['[']) {397		p.bump();398		expr(p);399		p.expect(T![']']);400		m.complete(p, FIELD_NAME_DYNAMIC);401	} else if p.at(IDENT) {402		name(p);403		m.complete(p, FIELD_NAME_FIXED);404	} else if Text::can_cast(p.current()) {405		text(p);406		m.complete(p, FIELD_NAME_FIXED);407	} else {408		m.forget(p);409		// Recover with ::, :::410		p.error_with_recovery_set(TS![; : '(']);411	}412}413fn visibility(p: &mut Parser) {414	let m = p.start();415	if !p.at_ts(TS![:]) {416		p.error_with_recovery_set(TS![=]);417	}418	p.bump();419	'colons: {420		if !p.at_ts(TS![:]) {421			break 'colons;422		}423		p.bump();424		if !p.at_ts(TS![:]) {425			break 'colons;426		}427		p.bump();428	}429	m.complete(p, VISIBILITY);430}431fn assertion(p: &mut Parser) {432	let m = p.start();433	p.bump_assert(T![assert]);434	expr(p);435	if p.at(T![:]) {436		p.bump();437		expr(p);438	}439	m.complete(p, ASSERTION);440}441fn object(p: &mut Parser) -> CompletedMarker {442	let m_t = p.start();443	let m = p.start();444	p.bump_assert(T!['{']);445446	let mut elems = 0;447	let mut compspecs = Vec::new();448	let mut asserts = Vec::new();449	loop {450		if p.at(T!['}']) {451			p.bump();452			break;453		}454		if p.at_ts(TS![for]) {455			if elems == 0 {456				let m = p.start();457				m.complete_missing(p, ExpectedSyntax::Named("field definition"));458			}459			while p.at_ts(COMPSPEC) {460				compspecs.push(compspec(p));461			}462			if comma_with_alternatives(p, TS![;]) {463				continue;464			}465			p.expect(R_BRACE);466			break;467		}468		let m = p.start();469		if p.at(T![local]) {470			obj_local(p);471			m.complete(p, MEMBER_BIND_STMT);472		} else if p.at(T![assert]) {473			assertion(p);474			asserts.push(m.complete(p, MEMBER_ASSERT_STMT));475		} else {476			field_name(p);477			if p.at(T![+]) {478				p.bump();479			}480			let params = if p.at(T!['(']) {481				params_desc(p);482				visibility(p);483				expr(p);484				true485			} else {486				visibility(p);487				if p.at(T![function]) {488					p.bump_assert(T![function]);489					params_desc(p);490					expr(p);491					true492				} else {493					expr(p);494					false495				}496			};497			elems += 1;498499			if params {500				m.complete(p, MEMBER_FIELD_METHOD)501			} else {502				m.complete(p, MEMBER_FIELD_NORMAL)503			};504		}505		while p.at_ts(COMPSPEC) {506			compspecs.push(compspec(p));507		}508		if comma_with_alternatives(p, TS![;]) {509			continue;510		}511		p.expect(R_BRACE);512		break;513	}514515	if elems > 1 && !compspecs.is_empty() {516		for errored in compspecs {517			errored.wrap_error(518				p,519				"compspec may only be used if there is only one object element",520				true,521			);522		}523		m.complete(p, OBJ_BODY_MEMBER_LIST);524	} else if !compspecs.is_empty() {525		for errored in asserts {526			errored.wrap_error(p, "asserts can't be used in object comprehensions", true);527		}528		m.complete(p, OBJ_BODY_COMP);529	} else {530		m.complete(p, OBJ_BODY_MEMBER_LIST);531	}532	m_t.complete(p, EXPR_OBJECT)533}534fn param(p: &mut Parser) {535	let m = p.start();536	destruct(p);537	if p.at(T![=]) {538		p.bump();539		expr(p);540	}541	m.complete(p, PARAM);542}543fn params_desc(p: &mut Parser) -> CompletedMarker {544	let m = p.start();545	p.bump_assert(T!['(']);546547	loop {548		if p.at(T![')']) {549			p.bump();550			break;551		}552		param(p);553		if comma(p) {554			continue;555		}556		p.expect(T![')']);557		break;558	}559560	m.complete(p, PARAMS_DESC)561}562fn args_desc(p: &mut Parser) {563	let m = p.start();564	p.bump_assert(T!['(']);565566	let started_named = Cell::new(false);567	let mut unnamed_after_named = Vec::new();568569	loop {570		if p.at(T![')']) {571			break;572		}573574		let m = p.start();575		if p.at(IDENT) && p.nth_at(1, T![=]) {576			name(p);577			p.bump();578			expr(p);579			m.complete(p, ARG);580			started_named.set(true);581		} else {582			expr(p);583			let arg = m.complete(p, ARG);584			if started_named.get() {585				unnamed_after_named.push(arg);586			}587		}588		if comma(p) {589			continue;590		}591		break;592	}593	p.expect(T![')']);594	if p.at(T![tailstrict]) {595		p.bump();596	}597598	for errored in unnamed_after_named {599		errored.wrap_error(p, "can't use positional arguments after named", true);600	}601602	m.complete(p, ARGS_DESC);603}604605fn array(p: &mut Parser) -> CompletedMarker {606	// Start the list node607	let m = p.start();608	p.bump_assert(T!['[']);609610	let mut compspecs = Vec::new();611	let mut elems = 0;612613	loop {614		if p.at(T![']']) {615			p.bump();616			break;617		}618		if elems != 0 && p.at_ts(TS![for]) {619			while p.at_ts(COMPSPEC) {620				compspecs.push(compspec(p));621			}622			if comma(p) {623				continue;624			}625			p.expect(T![']']);626			break;627		}628		expr(p);629		elems += 1;630		while p.at_ts(COMPSPEC) {631			compspecs.push(compspec(p));632		}633		if comma(p) {634			continue;635		}636		p.expect(T![']']);637		break;638	}639640	if elems > 1 && !compspecs.is_empty() {641		for spec in compspecs {642			spec.wrap_error(643				p,644				"compspec may only be used if there is only one array element",645				true,646			);647		}648649		m.complete(p, EXPR_ARRAY)650	} else if !compspecs.is_empty() {651		m.complete(p, EXPR_ARRAY_COMP)652	} else {653		m.complete(p, EXPR_ARRAY)654	}655}656/// Returns true if it was slice, false if just index657#[must_use]658fn slice_desc_or_index(p: &mut Parser) -> bool {659	let m = p.start();660	p.bump();661	// Start662	if !p.at(T![:]) {663		expr(p);664	}665	if p.at(T![:]) {666		p.bump();667		// End668		if !p.at_ts(TS![']' :]) {669			expr(p).wrap(p, SLICE_DESC_END, true);670		}671		if p.at(T![:]) {672			p.bump();673			// Step674			if !p.at(T![']']) {675				expr(p).wrap(p, SLICE_DESC_STEP, true);676			}677		}678	} else {679		// It was not a slice680		p.expect(T![']']);681		m.forget(p);682		return false;683	}684	p.expect(T![']']);685	m.complete(p, SLICE_DESC);686	true687}688689fn suffix(p: &mut Parser) {690	loop {691		let start = p.start();692		let _marker: CompletedMarker = if p.at(T![?]) {693			p.bump();694			p.expect(T![.]);695			if p.at(IDENT) {696				name(p);697				start.complete(p, SUFFIX_INDEX)698			} else if p.at(T!['[']) {699				p.bump();700				expr(p);701				p.expect(T![']']);702				start.complete(p, SUFFIX_INDEX_EXPR)703			} else {704				start.complete_missing(p, ExpectedSyntax::Named("index"))705			}706		} else if p.at(T![.]) {707			p.bump();708			name(p);709			start.complete(p, SUFFIX_INDEX)710		} else if p.at(T!['[']) {711			if slice_desc_or_index(p) {712				start.complete(p, SUFFIX_SLICE)713			} else {714				start.complete(p, SUFFIX_INDEX_EXPR)715			}716		} else if p.at(T!['(']) {717			args_desc(p);718			start.complete(p, SUFFIX_APPLY)719		} else {720			start.forget(p);721			break;722		};723	}724}725726fn lhs(p: &mut Parser) -> Result<CompletedMarker, CompletedMarker> {727	let lhs = lhs_basic(p)?;728729	suffix(p);730731	Ok(lhs)732}733fn name(p: &mut Parser) {734	let m = p.start();735	p.expect(IDENT);736	m.complete(p, NAME);737}738fn destruct_rest(p: &mut Parser) {739	let m = p.start();740	p.bump_assert(T![...]);741	if p.at(IDENT) {742		p.bump();743	}744	m.complete(p, DESTRUCT_REST);745}746fn destruct_object_field(p: &mut Parser) {747	let m = p.start();748	name(p);749	if p.at(T![:]) {750		p.bump();751		destruct(p);752	}753	if p.at(T![=]) {754		p.bump();755		expr(p);756	}757	m.complete(p, DESTRUCT_OBJECT_FIELD);758}759fn obj_local(p: &mut Parser) {760	let m = p.start();761	p.bump_assert(T![local]);762	bind(p);763	m.complete(p, OBJ_LOCAL);764}765fn destruct(p: &mut Parser) -> CompletedMarker {766	let m = p.start();767	let _ex = p.expected_syntax_name("destruction specifier");768	if p.at(T![?]) {769		p.bump();770		m.complete(p, DESTRUCT_SKIP)771	} else if p.at(T!['[']) {772		p.bump();773		// let mut had_rest = false;774		loop {775			if p.at(T![']']) {776				p.bump();777				break;778			} else if p.at(T![...]) {779				// let m_err = p.start_ranger();780				destruct_rest(p);781			// if had_rest {782			// 	p.custom_error(m_err.finish(p), "only one rest can be present in array");783			// }784			// had_rest = true;785			} else {786				destruct(p);787			}788			if p.at(T![,]) {789				p.bump();790				continue;791			}792			p.expect(T![']']);793			break;794		}795		m.complete(p, DESTRUCT_ARRAY)796	} else if p.at(T!['{']) {797		p.bump();798		let mut had_rest = false;799		loop {800			if p.at(T!['}']) {801				p.bump();802				break;803			} else if p.at(T![...]) {804				// let m_err = p.start_ranger();805				destruct_rest(p);806				// if had_rest {807				// 	p.custom_error(m_err.finish(p), "only one rest can be present in object");808				// }809				had_rest = true;810			} else {811				if had_rest {812					p.error_with_recovery_set(TS![]);813				}814				destruct_object_field(p);815			}816			if p.at(T![,]) {817				p.bump();818				continue;819			}820			p.expect(T!['}']);821			break;822		}823		m.complete(p, DESTRUCT_OBJECT)824	} else if p.at(IDENT) {825		name(p);826		m.complete(p, DESTRUCT_FULL)827	} else {828		m.forget(p);829		p.error_with_recovery_set(TS![; , '}', '(', :])830	}831}832fn bind(p: &mut Parser) {833	let m = p.start();834	if p.at(IDENT) && p.nth_at(1, T!['(']) {835		name(p);836		params_desc(p);837		p.expect(T![=]);838		expr(p);839		m.complete(p, BIND_FUNCTION)840	} else if p.at(IDENT) && p.nth_at(1, T![=]) && p.nth_at(2, T![function]) {841		name(p);842		p.expect(T![=]);843		p.expect(T![function]);844		params_desc(p);845		expr(p);846		m.complete(p, BIND_FUNCTION)847	} else {848		destruct(p);849		p.expect(T![=]);850		expr(p);851		m.complete(p, BIND_DESTRUCT)852	};853}854fn text(p: &mut Parser) {855	assert!(Text::can_cast(p.current()));856	p.bump();857}858fn number(p: &mut Parser) {859	assert!(Number::can_cast(p.current()));860	p.bump();861}862fn literal(p: &mut Parser) {863	assert!(Literal::can_cast(p.current()));864	p.bump();865}866fn lhs_basic(p: &mut Parser) -> Result<CompletedMarker, CompletedMarker> {867	let _e = p.expected_syntax_name("expression");868	Ok(if Literal::can_cast(p.current()) {869		let m = p.start();870		literal(p);871		m.complete(p, EXPR_LITERAL)872	} else if Text::can_cast(p.current()) {873		let m = p.start();874		text(p);875		m.complete(p, EXPR_STRING)876	} else if Number::can_cast(p.current()) {877		let m = p.start();878		number(p);879		m.complete(p, EXPR_NUMBER)880	} else if p.at(IDENT) {881		let m = p.start();882		name(p);883		m.complete(p, EXPR_VAR)884	} else if p.at(T![if]) {885		let m = p.start();886		p.bump();887		expr(p);888		p.expect(T![then]);889		expr(p).wrap(p, TRUE_EXPR, true);890		if p.at(T![else]) {891			p.bump();892			expr(p).wrap(p, FALSE_EXPR, true);893		}894		m.complete(p, EXPR_IF_THEN_ELSE)895	} else if p.at(T!['[']) {896		array(p)897	} else if p.at(T!['{']) {898		object(p)899	} else if p.at(T![function]) {900		let m = p.start();901		p.bump();902		params_desc(p);903		expr(p);904		m.complete(p, EXPR_FUNCTION)905	} else if p.at(T![error]) {906		let m = p.start();907		p.bump();908		expr(p);909		m.complete(p, EXPR_ERROR)910	} else if p.at(T![import]) || p.at(T![importstr]) || p.at(T![importbin]) {911		let m = p.start();912		p.bump();913		text(p);914		m.complete(p, EXPR_IMPORT)915	} else if let Some(op) = UnaryOperatorKind::cast(p.current()) {916		let ((), right_binding_power) = op.binding_power();917918		let m = p.start();919		p.bump();920		let _ = expr_binding_power(p, right_binding_power)921			.map(|v| v.precede(p).complete(p, EXPR));922		m.complete(p, EXPR_UNARY)923	} else if p.at(T!['(']) {924		let m = p.start();925		p.bump();926		expr(p);927		p.expect(T![')']);928		m.complete(p, EXPR_PARENED)929	} else {930		return Err(p.error_with_no_skip());931	})932}933934impl Parse {935	pub fn syntax(&self) -> SyntaxNode {936		SyntaxNode::new_root(self.green_node.clone())937	}938}
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_in_binop_rhs.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_in_binop_rhs.snap
@@ -0,0 +1,34 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "a + local x = 1; x\n"
+---
+SOURCE_FILE@0..19
+  EXPR@0..18
+    EXPR_BINARY@0..18
+      EXPR@0..1
+        EXPR_VAR@0..1
+          NAME@0..1
+            IDENT@0..1 "a"
+      WHITESPACE@1..2 " "
+      PLUS@2..3 "+"
+      WHITESPACE@3..4 " "
+      EXPR@4..18
+        STMT_LOCAL@4..16
+          LOCAL_KW@4..9 "local"
+          WHITESPACE@9..10 " "
+          BIND_DESTRUCT@10..15
+            DESTRUCT_FULL@10..11
+              NAME@10..11
+                IDENT@10..11 "x"
+            WHITESPACE@11..12 " "
+            ASSIGN@12..13 "="
+            WHITESPACE@13..14 " "
+            EXPR@14..15
+              EXPR_NUMBER@14..15
+                FLOAT@14..15 "1"
+          SEMI@15..16 ";"
+        WHITESPACE@16..17 " "
+        EXPR_VAR@17..18
+          NAME@17..18
+            IDENT@17..18 "x"
+  WHITESPACE@18..19 "\n"
modifiedcrates/jrsonnet-rowan-parser/src/tests.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/tests.rs
+++ b/crates/jrsonnet-rowan-parser/src/tests.rs
@@ -224,6 +224,10 @@
 	unary_not_in_call => r#"
 		std.assertEqual(!false, true)
 	"#
+
+	local_in_binop_rhs => r#"
+		a + local x = 1; x
+	"#
 );
 
 #[test]