diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs index b4048e1bd722..15f0fddddacf 100644 --- a/crates/typst/src/eval/mod.rs +++ b/crates/typst/src/eval/mod.rs @@ -68,7 +68,7 @@ use unicode_segmentation::UnicodeSegmentation; use self::func::{CapturesVisitor, Closure}; use crate::diag::{ - bail, error, warning, At, FileError, SourceDiagnostic, SourceResult, StrResult, + bail, error, warning, At, FileError, Hint, SourceDiagnostic, SourceResult, StrResult, Trace, Tracepoint, }; use crate::model::{ @@ -1962,11 +1962,24 @@ fn access_dict<'a>( ) -> SourceResult<&'a mut Dict> { match access.target().access(vm)? { Value::Dict(dict) => Ok(dict), - value => bail!( - access.target().span(), - "expected dictionary, found {}", - value.type_name(), - ), + value => { + let type_name = value.type_name(); + let span = access.target().span(); + if matches!( + value, // those types have their own field getters + Value::Symbol(_) | Value::Content(_) | Value::Module(_) | Value::Func(_) + ) { + bail!(span, "cannot mutate fields on {type_name}"); + } else if fields::fields_on(type_name).is_empty() { + bail!(span, "{type_name} does not have accessible fields"); + } else { + // type supports static fields, which don't yet have + // setters + Err(eco_format!("fields on {type_name} are not yet mutable")) + .hint(eco_format!("try creating a new {type_name} with the updated field value instead")) + .at(span) + } + } } } diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ index 6a2f1c7ca968..b257394f9a39 100644 --- a/tests/typ/compiler/dict.typ +++ b/tests/typ/compiler/dict.typ @@ -92,10 +92,3 @@ --- // Error: 3-15 cannot mutate a temporary value #((key: "val").other = "some") - ---- -#{ - let object = none - // Error: 3-9 expected dictionary, found none - object.property = "value" -} diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ index 5c28a92cfa6d..a99959ea6a56 100644 --- a/tests/typ/compiler/field.typ +++ b/tests/typ/compiler/field.typ @@ -148,3 +148,53 @@ #test((top + start).y, top) #test((bottom + end).y, bottom) #test((horizon + center).y, horizon) + +--- +#{ + let object = sym.eq.not + // Error: 3-9 cannot mutate fields on symbol + object.property = "value" +} + +--- +#{ + let object = [hi] + // Error: 3-9 cannot mutate fields on content + object.property = "value" +} + +--- +#{ + let object = calc + // Error: 3-9 cannot mutate fields on module + object.property = "value" +} + +--- +#{ + let object = calc.sin + // Error: 3-9 cannot mutate fields on function + object.property = "value" +} + +--- +#{ + let object = none + // Error: 3-9 none does not have accessible fields + object.property = "value" +} + +--- +#{ + let object = 10 + // Error: 3-9 integer does not have accessible fields + object.property = "value" +} + +--- +#{ + let s = 1pt + red + // Error: 3-4 fields on stroke are not yet mutable + // Hint: 3-4 try creating a new stroke with the updated field value instead + s.thickness = 5pt +}