chandeliers_err/
error.rs

1//! Error message generation.
2//!
3//! Here we provide the facilities to instanciate prebuilt error messages.
4//! The general structure is that each kind of error message will be implemented
5//! by a `struct` that implements [`IntoError`], where the blanks are filled
6//! in by the `struct` fields' [`Display`] and [`TrySpan`] `impl`s.
7
8use std::fmt::Display;
9
10use crate::Span;
11
12/// Anything that went wrong: a sequence of [Span] and associated message.
13pub type Error = Vec<(String, Option<Span>)>;
14
15/// Generate an [`Error`].
16#[expect(
17    clippy::module_name_repetitions,
18    reason = "Of course the trait contains the word 'Error'"
19)]
20pub trait IntoError {
21    /// Produce the sequence of spans and help messages.
22    fn into_err(self) -> Error;
23}
24
25/// Objects that can be converted to spans.
26pub trait TrySpan {
27    /// Try to get a span from the object (by default we don't get any,
28    /// but a wrapper might provide one)
29    fn try_span(&self) -> Option<Span> {
30        None
31    }
32}
33
34/// Always [`Some`].
35impl TrySpan for Span {
36    fn try_span(&self) -> Option<Span> {
37        Some(*self)
38    }
39}
40
41/// Trivial projection.
42impl<T: TrySpan> TrySpan for &T {
43    fn try_span(&self) -> Option<Span> {
44        (*self).try_span()
45    }
46}
47
48/// Trivial projection.
49impl<T: TrySpan> TrySpan for Option<T> {
50    fn try_span(&self) -> Option<Span> {
51        self.as_ref().and_then(TrySpan::try_span)
52    }
53}
54
55/// Trivial projection.
56impl<T: TrySpan, E> TrySpan for Result<T, E> {
57    fn try_span(&self) -> Option<Span> {
58        self.as_ref().ok().and_then(TrySpan::try_span)
59    }
60}
61
62/// Objects that have a different way that they can be seen as a span
63pub trait TryDefSite {
64    /// Try to get a span from the object (by default we don't get any,
65    /// but a wrapper might provide one)
66    fn try_def_site(&self) -> Option<Span> {
67        None
68    }
69}
70
71/// Always [`None`]: [`Span`] provides a usage site not a def site.
72/// Put it in a wrapper if you want one.
73impl TryDefSite for Span {}
74
75/// Trivial projection.
76impl<T: TryDefSite> TryDefSite for &T {
77    fn try_def_site(&self) -> Option<Span> {
78        (*self).try_def_site()
79    }
80}
81
82/// Trivial projection.
83impl<T: TryDefSite> TryDefSite for Option<T> {
84    fn try_def_site(&self) -> Option<Span> {
85        self.as_ref().and_then(TryDefSite::try_def_site)
86    }
87}
88
89/// Trivial projection.
90impl<T: TryDefSite, E> TryDefSite for Result<T, E> {
91    fn try_def_site(&self) -> Option<Span> {
92        self.as_ref().ok().and_then(TryDefSite::try_def_site)
93    }
94}
95
96/// Generic wraper that implements [`Display`] and [`TrySpan`] to wrap together
97/// items that don't implement both.
98pub struct DisplayTrySpan<T> {
99    /// Displayable part.
100    pub display: T,
101    /// Spannable part.
102    pub try_span: Option<Span>,
103}
104
105impl<T: Display> Display for DisplayTrySpan<T> {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        self.display.fmt(f)
108    }
109}
110
111impl<T> TrySpan for DisplayTrySpan<T> {
112    fn try_span(&self) -> Option<Span> {
113        self.try_span
114    }
115}
116
117/// An explicit error message with its span.
118/// This error construct is meant to be phased out in favor of a prebuilt error
119/// message.
120#[deprecated = "This is a nonspecific error constructor that should eventually become a prebuilt message."]
121pub struct Basic {
122    /// Error kind.
123    pub msg: String,
124    /// Error location.
125    pub span: Span,
126}
127
128#[allow(clippy::allow_attributes, reason = "Needs regular (un)commenting out")]
129#[allow(deprecated, reason = "Removing impl is breaking despite deprecation")]
130impl IntoError for Basic {
131    fn into_err(self) -> Error {
132        vec![(self.msg, Some(self.span))]
133    }
134}
135
136/// An explicit sequence of errors and spans
137/// This error construct is meant to be phased out in favor of a prebuilt error
138/// message.
139//#[deprecated = "This is a nonspecific error constructor that should eventually become a prebuilt message."]
140pub struct Raw {
141    /// Messages and their spans.
142    pub es: Vec<(String, Option<Span>)>,
143}
144
145#[allow(clippy::allow_attributes, reason = "Needs regular (un)commenting out")]
146#[allow(deprecated, reason = "Removing impl is breaking despite deprecation")]
147impl IntoError for Raw {
148    fn into_err(self) -> Error {
149        self.es
150    }
151}
152
153/// Boolean-like parameter for error generation.
154trait Condition {
155    /// Extract truth value.
156    fn truth(&self) -> bool;
157}
158
159impl Condition for bool {
160    fn truth(&self) -> bool {
161        *self
162    }
163}
164
165/// Auxiliary arms for `error_message!` to build just the message constructor.
166/// These recursively consume the stream of tokens that describe the message
167/// and produce the appropriate `push` operations.
168///
169/// This implements the syntax of error lines described in the
170/// main [`error_message`].
171macro_rules! error_message_aux_push {
172    ( $constructed:ident, ) => {}; // done
173    ( $constructed:ident, $fmt:tt @ $site:ident ; $($rest:tt)* ) => {
174        // Base case looks like ["foo" @ site]: "foo" is treated as a format
175        // string and `site` gives the `Span`.
176        $constructed.push((format!($fmt), $site.try_span()));
177        error_message_aux_push!($constructed, $($rest)*);
178    };
179    ( $constructed:ident, $fmt:tt @* if $site:ident ; $($rest:tt)* ) => {
180        // Only insert if `try_def_site` is defined.
181        if let Some(site) = $site.try_def_site() {
182            $constructed.push((format!($fmt), Some(site)));
183        }
184        error_message_aux_push!($constructed, $($rest)*);
185    };
186    ( $constructed:ident, for $iterator:ident => $fmt:tt @ $site:expr ; $($rest:tt)* ) => {
187        // Loop over the base case [for items => "foo" @ site]
188        for $iterator in $iterator {
189            $constructed.push((format!($fmt), $site.try_span()));
190        }
191        error_message_aux_push!($constructed, $($rest)*);
192    };
193    ( $constructed:ident, if $cond:ident => $fmt:tt @ $site:expr ; $($rest:tt)* ) => {
194        // Only insert if `cond` holds [if cond => "foo" @ site]
195        if $cond.truth() {
196            $constructed.push((format!($fmt), $site.try_span()));
197        }
198        error_message_aux_push!($constructed, $($rest)*);
199    };
200    ( $constructed:ident, $fmt:tt @ if $site:expr ; $($rest:tt)* ) => {
201        // Only insert if `try_span` is defined.
202        if let Some(site) = $site.try_span() {
203            $constructed.push((format!($fmt), Some(site)));
204        }
205        error_message_aux_push!($constructed, $($rest)*);
206    };
207    ( $constructed:ident, for $iterator:ident => $fmt:tt ; $($rest:tt)* ) => {
208        // Loop over the base case without a span [for items => "foo"]
209        for $iterator in $iterator {
210            $constructed.push((format!($fmt), None));
211        }
212        error_message_aux_push!($constructed, $($rest)*);
213    };
214    ( $constructed:ident, $fmt:tt ; $($rest:tt)* ) => {
215        // Base case without `Span`.
216        $constructed.push((format!($fmt), None));
217        error_message_aux_push!($constructed, $($rest)*);
218    };
219}
220
221/// More macro black magic.
222/// This one is supposed to reduce how repetitive it is to add new error messages.
223/// Describe the error message in a succint format and the macro will generate
224/// the `impl IntoError` automaticaly.
225///
226/// This defines a metalanguage to describe error messages.
227/// Each error consists of a declaration and an implementation.
228///
229/// The declaration looks like this:
230/// ```skip
231/// ["Documentation for SomeError"]
232/// struct SomeError where {
233///     ["Documentation for foo"] foo: {Display},
234///     ["Documentation for bar"] bar: {TrySpan},
235///     ["Documentation for quux"] quux: {Display + TrySpan},
236/// }
237/// ```
238/// As implied, this results in a struct having the fields `foo` and `bar`,
239/// and these fields can be of any type that implements the trait bounds.
240///
241/// The second part is the implementation
242/// ```skip
243/// impl {
244///     "Uh oh this is bad: {foo} occured here" @ bar;
245///     "Fix this construct {quux}" @ quux;
246/// }
247/// ```
248/// This will produce an error message that show is that order:
249/// - the first text line, formatted with `self.foo`
250/// - the span of `self.bar`
251/// - the second text line, formated with `self.quux`
252/// - the span of `self.quux`
253///
254/// The following further constructs are available to manipulate lines of messages
255/// in a more fine-grained manner:
256/// - `"msg"` plain message without span
257/// - `"{bar}"` format string (requires `bar: {Display}`)
258/// - `"msg" @ foo` use the span of `foo` (requires `foo: {TrySpan}`)
259/// - `"msg" @ if foo` use the span of `foo` but only if it is not `None` (requires `foo: {TrySpan}`)
260/// - `"msg" @* foo` use the def site of `foo` (requires `foo: {TryDefSite}`)
261/// - `"msg" @* if foo` use the def site of `foo` but only if it is not `None` (requires `foo: {TryDefSite}`)
262/// - `if cond => "msg" @ foo` only insert if `cond` holds (implements `Condition` that returns `true`)
263/// - `for items => "msg"` and `for items => "msg" @ foo`
264///   iterate over `items`, can be reused as a binding in both the format string and the span.
265///   Requires `items: [It .. Display + TrySpan]` (`Display` and `TrySpan` are optional depending
266///   on what you do with `items`) i.e. requires `items` to be an iterator of `Display + TrySpan`
267///   items.
268///
269/// See concrete syntax examples below.
270macro_rules! error_message {
271    (
272        $( [ $predoc:expr ] )* // documentation of the struct
273        struct $name:tt $( <$($explicit_generics:ident),*> )? where {
274            $( // fields and trait bounds (typically `Display` and/or `TrySpan`)
275                [ $doc:expr ]
276                $field:ident : $( [$item:ident .. $($iterbounds:tt)* ] )? $( { $($bounds:tt)+ } )?,
277            )*
278        } impl { // ;-separated list of messages, handled by the auxiliary arms
279            $( $message:tt )*
280        }
281    ) => {
282        #[expect(non_camel_case_types, reason = "Generic type has same name as field")]
283        $( #[doc = $predoc] )*
284        pub struct $name <$($field),*> {
285            $(
286                #[doc = $doc]
287                pub $field : $field ,
288            )*
289        }
290
291        #[expect(non_camel_case_types, reason = "Generic type has same name as field")]
292        impl <$($($explicit_generics),*,)? $($field),*> IntoError for $name<$($field),*>
293        where $(
294            $field: $( IntoIterator<Item = $item>, $item: $($iterbounds)* )?
295                $( $($bounds)* , )? )*
296        {
297            fn into_err(self) -> Error {
298                let Self { $($field),* } = self;
299                let mut constructed = Vec::new();
300                error_message_aux_push!(constructed,
301                    $($message)* // more black magic to turn these into statements
302                );
303                constructed
304            }
305        }
306    };
307}
308
309error_message! {
310    ["Generate an error for incompatible types between a \"left\" and a \"right\" values."]
311    struct TypeMismatch where {
312        ["Span of the entire error"] source: {TrySpan},
313        ["Left expression"] left: {Display + TrySpan},
314        ["Right expression"] right: {Display + TrySpan},
315        ["Further details on mismatch"] msg: {Display},
316    } impl {
317        "Type mismatch between the left and right sides: {msg}" @ source;
318        "This element has type {left}" @ left;
319        "While this element has type {right}" @ right;
320    }
321}
322
323/// Wrapper to display suggestions.
324pub struct Suggest<Its> {
325    /// Iterator of suggested items
326    pub available: Its,
327}
328
329impl<Its, It> Display for Suggest<Its>
330where
331    Its: IntoIterator<Item = It> + Clone,
332    It: Display,
333{
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        let mut suggest = self
336            .available
337            .clone()
338            .into_iter()
339            .map(|v| format!("{v}"))
340            .collect::<Vec<_>>();
341        suggest.sort();
342        if suggest.is_empty() {
343            write!(f, "(none declared)")
344        } else {
345            write!(f, "{}", suggest.join(", "))
346        }
347    }
348}
349
350error_message! {
351    ["Generate an error for a variable that was not declared yet."]
352    struct VarNotFound where {
353        ["What is missing"] var: {Display + TrySpan},
354        ["Local variable suggestions."] suggest1: {Display},
355        ["Global variable suggestions."] suggest2: {Display},
356    } impl {
357        "Variable {var} not found in the context." @ var;
358        "Perhaps you meant one of the local variables: {suggest1}";
359        "or one of the global variables: {suggest2}";
360    }
361}
362
363error_message! {
364    ["Generate an error for an undeclared function."]
365    struct FunNotFound where {
366        ["Invocation of missing function"] fun: {Display + TrySpan},
367    } impl {
368        "{fun} is not a known function in this scope." @ fun;
369    }
370}
371
372error_message! {
373    ["Generate an error for a type variable that was not declared yet,"]
374    ["which has consequences on what we should say is and isn't available."]
375    struct TyVarNotFound where {
376        ["What is missing."] var: {Display + TrySpan},
377        ["Local variable suggestions."] suggest: {Display},
378    } impl {
379        "Variable {var} is not available yet at this point of the typechecking." @ var;
380        "During this incremental typechecking, you cannot access global
381variables and you may only use local variables that have been
382declared strictly beforehand, in the order of inputs then outputs
383then locals.";
384        "These are the variables that are already useable: {suggest}";
385    }
386}
387
388error_message! {
389    ["Generate an erorr for an expression that is noot valid in a `const` declaration."]
390    struct NotConst where {
391        ["Description of the invalid expression constructor."] what: {Display},
392        ["Location of the error."] site: {TrySpan},
393    } impl {
394        "{what} not valid in const contexts" @ site;
395        "You must put this definition inside a node";
396    }
397}
398
399error_message! {
400    ["Generate an error for a binary operator that expected arguments of a specific type."]
401    struct BinopMismatch where {
402        ["Description of the operator."] oper: {Display},
403        ["Location of the error."] site: {TrySpan},
404        ["What we expected in place of the arguments."] expect: {Display},
405        ["Left hand side and span."] left: {Display + TrySpan},
406        ["Right hand side and span."] right: {Display + TrySpan},
407    } impl {
408        "Binary operator `{oper}` expects arguments of {expect}" @ site;
409        "The left-hand-side is found to be of type {left}" @ left;
410        "The right-hand-side is found to be of type {right}" @ right;
411    }
412}
413
414error_message! {
415    ["Generate an error for a unary operator that expected an argument of a specific type."]
416    struct UnopMismatch where {
417        ["Description of the operator."] oper: {Display},
418        ["What the operator expects."] expect: {Display},
419        ["Location of the error."] site: {TrySpan},
420        ["Invalid expression and span."] inner: {Display + TrySpan},
421    } impl {
422        "Unary operator `{oper}` expects an argument of {expect}" @ site;
423        "The inner value is found to be of type {inner}" @ inner;
424    }
425}
426
427error_message! {
428    ["Generate an error for something that should have been a bool but isn't,"]
429    ["e.g. `if 1 then 0 else 1`."]
430    struct BoolRequired where {
431        ["Explanation of what this item is (e.g. \"the condition of if\")"] what: {Display},
432        ["Location of the error."] site: {TrySpan},
433        ["Location of the inner contents."] inner: {Display + TrySpan},
434    } impl {
435        "{what} should be of type bool" @ site;
436        "The argument is found to be of type {inner}" @ inner;
437    }
438}
439
440error_message! {
441    ["Generate an error for a cyclic definition."]
442    struct Cycle<Its> where {
443        ["Beginning of the cycle"] head: {Display + TrySpan},
444        ["Rest of the cycle (not necessarily ordered)."] items: [Its .. TrySpan + Display],
445    } impl {
446        "{head} was found to be part of a dependency cycle" @ head;
447        for items => "The cycle also goes through {items}" @ items;
448    }
449}
450
451error_message! {
452    ["Error message for an object that was defined twice when only one"]
453    ["declaration should exist."]
454    struct GraphUnitDeclTwice where {
455        ["Display of the redefined object."] unit: {Display},
456        ["Location of the superfluous definition."] new_site: {TrySpan},
457        ["Item that defined the object previously."] prior: {Display},
458        ["Location of the first definition."] prior_site: {TrySpan},
459    } impl {
460        "Attempt to redefine {unit}, when {prior} already defines it" @ new_site;
461        "Already defined here" @ prior_site;
462    }
463}
464
465error_message! {
466    ["Error for an object that should have been declared but was not."]
467    struct GraphUnitUndeclared where {
468        ["Missing object and site where usage was attempted."] unit: {Display + TrySpan},
469    } impl {
470        "No definition provided for {unit} which is required" @ unit;
471    }
472}
473
474error_message! {
475    ["Special case of [Cycle]: custom message for an object that depends"]
476    ["specifically on itself directly."]
477    struct GraphUnitDependsOnItself where {
478        ["Object that loops."] unit: {Display},
479        ["Where it is defined."] def_site: {TrySpan},
480        ["Where it is used (usually a subspan of `def_site`)."] usage: {TrySpan},
481    } impl {
482        "{unit} depends on itself" @ def_site;
483        "used here within its own definition" @ usage;
484    }
485}
486
487error_message! {
488    ["Error for when one tried to access too far into the past."]
489    struct NotPositive where {
490        ["Variable that is not deep enough."] var: {Display},
491        ["Location of the error"] site: {TrySpan},
492        ["How deep we could have gone"] available_depth: {Display},
493        ["How deep we actually tried to go"] attempted_depth: {Display},
494    } impl {
495        "Variable {var} is not positive at this depth" @ site;
496        "tried to reach {attempted_depth} steps into the past, with only {available_depth} available";
497        "Maybe add a `->` in front of the expression to increase the depth ?";
498    }
499}
500
501error_message! {
502    ["Attempted to use a `pre` in register mode without any depth available"]
503    struct ShallowPre where {
504        ["Expression to compute"] expr: {TrySpan},
505    } impl {
506        "Cannot compute the `pre` of this expression because the context does not provide an initialization value" @ expr;
507        "Maybe add a `->` in front of the expression to increase the depth ?";
508    }
509}
510
511error_message! {
512    ["Error for a literal that is not supported."]
513    ["Lustre only has `float`, `int`, and `bool` literals, so e.g. a `&str` will trigger this error."]
514    struct UnhandledLitType where {
515        ["Location of the literal."] site: {TrySpan},
516    } impl {
517        "Lustre only accepts literals of type int, float, or bool" @ site;
518    }
519}
520
521error_message! {
522    ["Error for when a comparison operator is used with associativity."]
523    ["Since `a = b = c` is ambiguous (does it mean `(a = b) = c` or `a = (b = c)`"]
524    ["or `(a = b) and (b = c)`, we choose to reject all interpretations and"]
525    ["ask for explicit parentheses around comparison operators."]
526    struct CmpNotAssociative where {
527        ["The `<` of `a < b > c`"] oper1: {Display},
528        ["The `a` of `a < b > c`"] first: {Display},
529        ["The whole location of `a < b > c`"] site: {TrySpan},
530        ["The `b` of `a < b > c`"] second: {Display},
531        ["The `c` of `a < b > c`"] third: {Display},
532        ["The `>` of `a < b > c`"] oper2: {Display},
533    } impl {
534        "Comparison operator {oper1} is not associative" @ site;
535        "Maybe replace `{first} {oper1} {second} {oper2} {third}` with `{first} {oper1} {second} and {second} {oper2} {third}` ?";
536    }
537}
538
539error_message! {
540    ["Generate an error when due to `implicit` that has the implicit clock,"]
541    ["`slow` was expected to also have the implicit clock but doesn't."]
542    struct ClkTooSlowExpectImplicit<It> where {
543        ["Clocked by something else, should have been `'self`."] slow: {Display + TrySpan + TryDefSite},
544        ["Clocked by `'self`"] implicit: {TrySpan},
545        ["Extra help messages, optionally."] extra: [It .. Display],
546    } impl {
547        "This expression is too slow: expected the implicit clock 'self, found {slow}" @ slow;
548        "Found {slow} here" @* if slow;
549        "Expected because this expression moves at the implicit pace" @ if implicit;
550        for extra => "{extra}";
551    }
552}
553
554error_message! {
555    ["When an expression is not a valid clock (anything but a local variable)."]
556    struct NotAClock where {
557        ["The faulty expression."] expr: {Display + TrySpan},
558    } impl {
559        "The expression `{expr}` cannot be interpreted as a clock because it is not a local boolean variable" @ expr;
560    }
561}
562
563error_message! {
564    ["When two clocks are both non-implicit but different."]
565    struct ClkNotComparable where {
566        ["First clock."] first: {Display + TrySpan + TryDefSite},
567        ["Second clock."] second: {Display + TrySpan + TryDefSite},
568        ["Span of the whole expression that contains both."] whole: {TrySpan},
569    } impl {
570        "Two subexpressions have incomparable clocks: {first} and {second} are incompatible" @ whole;
571        "This is clocked by {first}" @ first;
572        "defined here" @* if first;
573        "This is clocked by {second}" @ second;
574        "defined here" @* if second;
575    }
576}
577
578error_message! {
579    ["When two clocks should have been identical."]
580    struct ClkNotIdentical where {
581        ["First clock."] first: {Display + TrySpan + TryDefSite},
582        ["Second clock."] second: {Display + TrySpan + TryDefSite},
583        ["Span of the whole expression that contains both."] whole: {TrySpan},
584    } impl {
585        "Two subexpressions have different clocks: {first} and {second} are incompatible" @ whole;
586        "This is clocked by {first}" @ first;
587        "defined here" @* if first;
588        "This is clocked by {second}" @ second;
589        "defined here" @* if second;
590        "some operations (notably assignment) have stricter requirements than others on clock equality";
591        "you may need to insert a `when` operator";
592    }
593}
594
595error_message! {
596    ["When a generic type variable is unused and thus not inferrable"]
597    struct UnusedGeneric where {
598        ["Type variable that is absent from the inputs declaration."] unused: {Display + TrySpan},
599        ["Declaration of said inputs."] inputs: {TrySpan},
600    } impl {
601        "Type variable {unused} cannot be inferred from the inputs of this node" @ unused;
602        "None of these arguments are of type {unused}" @ inputs;
603    }
604}
605
606error_message! {
607    ["Impossible to satisfy generic constraints introduced by bounds."]
608    struct UnsatGenericConstraint where {
609        ["Type variable."] variable: {Display},
610        ["Already equal to."] previous: {Display + TrySpan},
611        ["Now additionally required to be equal to."] new: {Display + TrySpan},
612        ["Bound introduced by this node declaration."] context: {TrySpan},
613    } impl {
614        "Cannot satisfy constraint {variable} = {new} introduced here..." @ new;
615        "...because a previous bound already enforces {variable} = {previous}" @ previous;
616        "Unable to satisfy the generic bounds on {variable}" @ context;
617    }
618}
619
620error_message! {
621    ["When a generic type variable is unused and thus not inferrable."]
622    struct UndeclaredGeneric where {
623        ["Type variable that is absent from the generics declaration."] undeclared: {Display + TrySpan},
624    } impl {
625        "Type variable {undeclared} was not declared" @ undeclared;
626        "Maybe add a `#[generic[{undeclared}]]` annotation to the node ?";
627    }
628}
629
630error_message! {
631    ["Node is declared as executable, but has nonempty inputs or outputs."]
632    struct ExecutableNodeSig where {
633        ["Attribute that marks it as executable."] reason: {Display},
634        ["Whether there are any inputs."] inputs_nonempty: {Condition},
635        ["Where are the inputs."] inputs: {TrySpan},
636        ["Whether there are any outputs."] outputs_nonempty: {Condition},
637        ["Where are the outputs."] outputs: {TrySpan},
638        ["Entire call site."] site: {TrySpan},
639    } impl {
640        "Node has an incompatible signature to be marked as executable (required due to {reason})" @ site;
641        if inputs_nonempty => "Inputs should be ()" @ inputs;
642        if outputs_nonempty => "Outputs should be ()" @ outputs;
643    }
644}
645
646error_message! {
647    ["Two types cannot be equal because one is a tuple and the other a scalar"]
648    struct ScalarNotTuple where {
649        ["Tuple type found"] typ: {Display + TrySpan},
650    } impl {
651        "Expected a scalar type, found a tuple {typ}" @ typ;
652    }
653}
654
655error_message! {
656    ["Attribute cannot apply to this language construct."]
657    struct InapplicableAttribute where {
658        ["Description"] attr: {Display},
659        ["Language item in question."] construct: {Display},
660        ["Location"] site: {TrySpan},
661    } impl {
662        "Attribute {attr} is not applicable to {construct}" @ site;
663    }
664}