diff --git a/crates/ltk_ritobin/src/parse.rs b/crates/ltk_ritobin/src/parse.rs index d202a7e9..4ea00ef6 100644 --- a/crates/ltk_ritobin/src/parse.rs +++ b/crates/ltk_ritobin/src/parse.rs @@ -131,6 +131,16 @@ entries: map[hash, embed] = { assert_success(r#"mVelMultiplier: f32 = 0 # asd"#); } + #[test] + fn unterminated_string_value() { + let text = "name: string = \"\n"; + let cst = Cst::parse(text); + assert!(!cst.errors.is_empty()); + + // used to panic on the error tree left behind by the tokenizer + let (_bin, _errors) = cst.build_bin(text); + } + #[ignore = "Nice to have"] #[test] fn naked_class() { diff --git a/crates/ltk_ritobin/src/typecheck/resolve.rs b/crates/ltk_ritobin/src/typecheck/resolve.rs index 83e95e11..5e247d21 100644 --- a/crates/ltk_ritobin/src/typecheck/resolve.rs +++ b/crates/ltk_ritobin/src/typecheck/resolve.rs @@ -368,7 +368,11 @@ pub(crate) fn resolve_value( let Some(child) = children.get(visit_ctx.cst).first() else { return Ok(None); }; - return resolve_literal(ctx, child.token(visit_ctx.cst).unwrap(), kind_hint); + // an unterminated string leaves an error tree here instead of a token + let Some(token) = child.token(visit_ctx.cst) else { + return Ok(None); + }; + return resolve_literal(ctx, token, kind_hint); } _ => return Ok(None), }))