]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_errors/src/diagnostic.rs
New upstream version 1.60.0+dfsg1
[rustc.git] / compiler / rustc_errors / src / diagnostic.rs
1 use crate::snippet::Style;
2 use crate::CodeSuggestion;
3 use crate::Level;
4 use crate::Substitution;
5 use crate::SubstitutionPart;
6 use crate::SuggestionStyle;
7 use crate::ToolMetadata;
8 use rustc_lint_defs::Applicability;
9 use rustc_serialize::json::Json;
10 use rustc_span::{MultiSpan, Span, DUMMY_SP};
11 use std::fmt;
12 use std::hash::{Hash, Hasher};
13
14 /// Error type for `Diagnostic`'s `suggestions` field, indicating that
15 /// `.disable_suggestions()` was called on the `Diagnostic`.
16 #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
17 pub struct SuggestionsDisabled;
18
19 #[must_use]
20 #[derive(Clone, Debug, Encodable, Decodable)]
21 pub struct Diagnostic {
22 pub level: Level,
23 pub message: Vec<(String, Style)>,
24 pub code: Option<DiagnosticId>,
25 pub span: MultiSpan,
26 pub children: Vec<SubDiagnostic>,
27 pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
28
29 /// This is not used for highlighting or rendering any error message. Rather, it can be used
30 /// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
31 /// `span` if there is one. Otherwise, it is `DUMMY_SP`.
32 pub sort_span: Span,
33
34 /// If diagnostic is from Lint, custom hash function ignores notes
35 /// otherwise hash is based on the all the fields
36 pub is_lint: bool,
37 }
38
39 #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
40 pub enum DiagnosticId {
41 Error(String),
42 Lint { name: String, has_future_breakage: bool, is_force_warn: bool },
43 }
44
45 /// A "sub"-diagnostic attached to a parent diagnostic.
46 /// For example, a note attached to an error.
47 #[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
48 pub struct SubDiagnostic {
49 pub level: Level,
50 pub message: Vec<(String, Style)>,
51 pub span: MultiSpan,
52 pub render_span: Option<MultiSpan>,
53 }
54
55 #[derive(Debug, PartialEq, Eq)]
56 pub struct DiagnosticStyledString(pub Vec<StringPart>);
57
58 impl DiagnosticStyledString {
59 pub fn new() -> DiagnosticStyledString {
60 DiagnosticStyledString(vec![])
61 }
62 pub fn push_normal<S: Into<String>>(&mut self, t: S) {
63 self.0.push(StringPart::Normal(t.into()));
64 }
65 pub fn push_highlighted<S: Into<String>>(&mut self, t: S) {
66 self.0.push(StringPart::Highlighted(t.into()));
67 }
68 pub fn push<S: Into<String>>(&mut self, t: S, highlight: bool) {
69 if highlight {
70 self.push_highlighted(t);
71 } else {
72 self.push_normal(t);
73 }
74 }
75 pub fn normal<S: Into<String>>(t: S) -> DiagnosticStyledString {
76 DiagnosticStyledString(vec![StringPart::Normal(t.into())])
77 }
78
79 pub fn highlighted<S: Into<String>>(t: S) -> DiagnosticStyledString {
80 DiagnosticStyledString(vec![StringPart::Highlighted(t.into())])
81 }
82
83 pub fn content(&self) -> String {
84 self.0.iter().map(|x| x.content()).collect::<String>()
85 }
86 }
87
88 #[derive(Debug, PartialEq, Eq)]
89 pub enum StringPart {
90 Normal(String),
91 Highlighted(String),
92 }
93
94 impl StringPart {
95 pub fn content(&self) -> &str {
96 match self {
97 &StringPart::Normal(ref s) | &StringPart::Highlighted(ref s) => s,
98 }
99 }
100 }
101
102 impl Diagnostic {
103 pub fn new(level: Level, message: &str) -> Self {
104 Diagnostic::new_with_code(level, None, message)
105 }
106
107 pub fn new_with_code(level: Level, code: Option<DiagnosticId>, message: &str) -> Self {
108 Diagnostic {
109 level,
110 message: vec![(message.to_owned(), Style::NoStyle)],
111 code,
112 span: MultiSpan::new(),
113 children: vec![],
114 suggestions: Ok(vec![]),
115 sort_span: DUMMY_SP,
116 is_lint: false,
117 }
118 }
119
120 pub fn is_error(&self) -> bool {
121 match self.level {
122 Level::Bug | Level::Fatal | Level::Error { .. } | Level::FailureNote => true,
123
124 Level::Warning | Level::Note | Level::Help | Level::Cancelled | Level::Allow => false,
125 }
126 }
127
128 pub fn has_future_breakage(&self) -> bool {
129 match self.code {
130 Some(DiagnosticId::Lint { has_future_breakage, .. }) => has_future_breakage,
131 _ => false,
132 }
133 }
134
135 pub fn is_force_warn(&self) -> bool {
136 match self.code {
137 Some(DiagnosticId::Lint { is_force_warn, .. }) => is_force_warn,
138 _ => false,
139 }
140 }
141
142 /// Cancel the diagnostic (a structured diagnostic must either be emitted or
143 /// canceled or it will panic when dropped).
144 pub fn cancel(&mut self) {
145 self.level = Level::Cancelled;
146 }
147
148 /// Check if this diagnostic [was cancelled][Self::cancel()].
149 pub fn cancelled(&self) -> bool {
150 self.level == Level::Cancelled
151 }
152
153 /// Adds a span/label to be included in the resulting snippet.
154 ///
155 /// This is pushed onto the [`MultiSpan`] that was created when the diagnostic
156 /// was first built. That means it will be shown together with the original
157 /// span/label, *not* a span added by one of the `span_{note,warn,help,suggestions}` methods.
158 ///
159 /// This span is *not* considered a ["primary span"][`MultiSpan`]; only
160 /// the `Span` supplied when creating the diagnostic is primary.
161 pub fn span_label<T: Into<String>>(&mut self, span: Span, label: T) -> &mut Self {
162 self.span.push_span_label(span, label.into());
163 self
164 }
165
166 pub fn replace_span_with(&mut self, after: Span) -> &mut Self {
167 let before = self.span.clone();
168 self.set_span(after);
169 for span_label in before.span_labels() {
170 if let Some(label) = span_label.label {
171 self.span_label(after, label);
172 }
173 }
174 self
175 }
176
177 crate fn note_expected_found(
178 &mut self,
179 expected_label: &dyn fmt::Display,
180 expected: DiagnosticStyledString,
181 found_label: &dyn fmt::Display,
182 found: DiagnosticStyledString,
183 ) -> &mut Self {
184 self.note_expected_found_extra(expected_label, expected, found_label, found, &"", &"")
185 }
186
187 crate fn note_unsuccessful_coercion(
188 &mut self,
189 expected: DiagnosticStyledString,
190 found: DiagnosticStyledString,
191 ) -> &mut Self {
192 let mut msg: Vec<_> =
193 vec![("required when trying to coerce from type `".to_string(), Style::NoStyle)];
194 msg.extend(expected.0.iter().map(|x| match *x {
195 StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
196 StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
197 }));
198 msg.push(("` to type '".to_string(), Style::NoStyle));
199 msg.extend(found.0.iter().map(|x| match *x {
200 StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
201 StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
202 }));
203 msg.push(("`".to_string(), Style::NoStyle));
204
205 // For now, just attach these as notes
206 self.highlighted_note(msg);
207 self
208 }
209
210 pub fn note_expected_found_extra(
211 &mut self,
212 expected_label: &dyn fmt::Display,
213 expected: DiagnosticStyledString,
214 found_label: &dyn fmt::Display,
215 found: DiagnosticStyledString,
216 expected_extra: &dyn fmt::Display,
217 found_extra: &dyn fmt::Display,
218 ) -> &mut Self {
219 let expected_label = expected_label.to_string();
220 let expected_label = if expected_label.is_empty() {
221 "expected".to_string()
222 } else {
223 format!("expected {}", expected_label)
224 };
225 let found_label = found_label.to_string();
226 let found_label = if found_label.is_empty() {
227 "found".to_string()
228 } else {
229 format!("found {}", found_label)
230 };
231 let (found_padding, expected_padding) = if expected_label.len() > found_label.len() {
232 (expected_label.len() - found_label.len(), 0)
233 } else {
234 (0, found_label.len() - expected_label.len())
235 };
236 let mut msg: Vec<_> =
237 vec![(format!("{}{} `", " ".repeat(expected_padding), expected_label), Style::NoStyle)];
238 msg.extend(expected.0.iter().map(|x| match *x {
239 StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
240 StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
241 }));
242 msg.push((format!("`{}\n", expected_extra), Style::NoStyle));
243 msg.push((format!("{}{} `", " ".repeat(found_padding), found_label), Style::NoStyle));
244 msg.extend(found.0.iter().map(|x| match *x {
245 StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
246 StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
247 }));
248 msg.push((format!("`{}", found_extra), Style::NoStyle));
249
250 // For now, just attach these as notes.
251 self.highlighted_note(msg);
252 self
253 }
254
255 pub fn note_trait_signature(&mut self, name: String, signature: String) -> &mut Self {
256 self.highlighted_note(vec![
257 (format!("`{}` from trait: `", name), Style::NoStyle),
258 (signature, Style::Highlight),
259 ("`".to_string(), Style::NoStyle),
260 ]);
261 self
262 }
263
264 /// Add a note attached to this diagnostic.
265 pub fn note(&mut self, msg: &str) -> &mut Self {
266 self.sub(Level::Note, msg, MultiSpan::new(), None);
267 self
268 }
269
270 pub fn highlighted_note(&mut self, msg: Vec<(String, Style)>) -> &mut Self {
271 self.sub_with_highlights(Level::Note, msg, MultiSpan::new(), None);
272 self
273 }
274
275 /// Prints the span with a note above it.
276 /// This is like [`Diagnostic::note()`], but it gets its own span.
277 crate fn span_note<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
278 self.sub(Level::Note, msg, sp.into(), None);
279 self
280 }
281
282 /// Add a warning attached to this diagnostic.
283 crate fn warn(&mut self, msg: &str) -> &mut Self {
284 self.sub(Level::Warning, msg, MultiSpan::new(), None);
285 self
286 }
287
288 /// Prints the span with a warning above it.
289 /// This is like [`Diagnostic::warn()`], but it gets its own span.
290 crate fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
291 self.sub(Level::Warning, msg, sp.into(), None);
292 self
293 }
294
295 /// Add a help message attached to this diagnostic.
296 crate fn help(&mut self, msg: &str) -> &mut Self {
297 self.sub(Level::Help, msg, MultiSpan::new(), None);
298 self
299 }
300
301 /// Prints the span with some help above it.
302 /// This is like [`Diagnostic::help()`], but it gets its own span.
303 crate fn span_help<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
304 self.sub(Level::Help, msg, sp.into(), None);
305 self
306 }
307
308 /// Disallow attaching suggestions this diagnostic.
309 /// Any suggestions attached e.g. with the `span_suggestion_*` methods
310 /// (before and after the call to `disable_suggestions`) will be ignored.
311 pub fn disable_suggestions(&mut self) -> &mut Self {
312 self.suggestions = Err(SuggestionsDisabled);
313 self
314 }
315
316 /// Helper for pushing to `self.suggestions`, if available (not disable).
317 fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
318 if let Ok(suggestions) = &mut self.suggestions {
319 suggestions.push(suggestion);
320 }
321 }
322
323 /// Show a suggestion that has multiple parts to it.
324 /// In other words, multiple changes need to be applied as part of this suggestion.
325 pub fn multipart_suggestion(
326 &mut self,
327 msg: &str,
328 suggestion: Vec<(Span, String)>,
329 applicability: Applicability,
330 ) -> &mut Self {
331 self.multipart_suggestion_with_style(
332 msg,
333 suggestion,
334 applicability,
335 SuggestionStyle::ShowCode,
336 )
337 }
338
339 /// Show a suggestion that has multiple parts to it, always as it's own subdiagnostic.
340 /// In other words, multiple changes need to be applied as part of this suggestion.
341 pub fn multipart_suggestion_verbose(
342 &mut self,
343 msg: &str,
344 suggestion: Vec<(Span, String)>,
345 applicability: Applicability,
346 ) -> &mut Self {
347 self.multipart_suggestion_with_style(
348 msg,
349 suggestion,
350 applicability,
351 SuggestionStyle::ShowAlways,
352 )
353 }
354 /// [`Diagnostic::multipart_suggestion()`] but you can set the [`SuggestionStyle`].
355 pub fn multipart_suggestion_with_style(
356 &mut self,
357 msg: &str,
358 suggestion: Vec<(Span, String)>,
359 applicability: Applicability,
360 style: SuggestionStyle,
361 ) -> &mut Self {
362 assert!(!suggestion.is_empty());
363 self.push_suggestion(CodeSuggestion {
364 substitutions: vec![Substitution {
365 parts: suggestion
366 .into_iter()
367 .map(|(span, snippet)| SubstitutionPart { snippet, span })
368 .collect(),
369 }],
370 msg: msg.to_owned(),
371 style,
372 applicability,
373 tool_metadata: Default::default(),
374 });
375 self
376 }
377
378 /// Prints out a message with for a multipart suggestion without showing the suggested code.
379 ///
380 /// This is intended to be used for suggestions that are obvious in what the changes need to
381 /// be from the message, showing the span label inline would be visually unpleasant
382 /// (marginally overlapping spans or multiline spans) and showing the snippet window wouldn't
383 /// improve understandability.
384 pub fn tool_only_multipart_suggestion(
385 &mut self,
386 msg: &str,
387 suggestion: Vec<(Span, String)>,
388 applicability: Applicability,
389 ) -> &mut Self {
390 assert!(!suggestion.is_empty());
391 self.push_suggestion(CodeSuggestion {
392 substitutions: vec![Substitution {
393 parts: suggestion
394 .into_iter()
395 .map(|(span, snippet)| SubstitutionPart { snippet, span })
396 .collect(),
397 }],
398 msg: msg.to_owned(),
399 style: SuggestionStyle::CompletelyHidden,
400 applicability,
401 tool_metadata: Default::default(),
402 });
403 self
404 }
405
406 /// Prints out a message with a suggested edit of the code.
407 ///
408 /// In case of short messages and a simple suggestion, rustc displays it as a label:
409 ///
410 /// ```text
411 /// try adding parentheses: `(tup.0).1`
412 /// ```
413 ///
414 /// The message
415 ///
416 /// * should not end in any punctuation (a `:` is added automatically)
417 /// * should not be a question (avoid language like "did you mean")
418 /// * should not contain any phrases like "the following", "as shown", etc.
419 /// * may look like "to do xyz, use" or "to do xyz, use abc"
420 /// * may contain a name of a function, variable, or type, but not whole expressions
421 ///
422 /// See `CodeSuggestion` for more information.
423 pub fn span_suggestion(
424 &mut self,
425 sp: Span,
426 msg: &str,
427 suggestion: String,
428 applicability: Applicability,
429 ) -> &mut Self {
430 self.span_suggestion_with_style(
431 sp,
432 msg,
433 suggestion,
434 applicability,
435 SuggestionStyle::ShowCode,
436 );
437 self
438 }
439
440 /// [`Diagnostic::span_suggestion()`] but you can set the [`SuggestionStyle`].
441 pub fn span_suggestion_with_style(
442 &mut self,
443 sp: Span,
444 msg: &str,
445 suggestion: String,
446 applicability: Applicability,
447 style: SuggestionStyle,
448 ) -> &mut Self {
449 self.push_suggestion(CodeSuggestion {
450 substitutions: vec![Substitution {
451 parts: vec![SubstitutionPart { snippet: suggestion, span: sp }],
452 }],
453 msg: msg.to_owned(),
454 style,
455 applicability,
456 tool_metadata: Default::default(),
457 });
458 self
459 }
460
461 /// Always show the suggested change.
462 pub fn span_suggestion_verbose(
463 &mut self,
464 sp: Span,
465 msg: &str,
466 suggestion: String,
467 applicability: Applicability,
468 ) -> &mut Self {
469 self.span_suggestion_with_style(
470 sp,
471 msg,
472 suggestion,
473 applicability,
474 SuggestionStyle::ShowAlways,
475 );
476 self
477 }
478
479 /// Prints out a message with multiple suggested edits of the code.
480 /// See also [`Diagnostic::span_suggestion()`].
481 pub fn span_suggestions(
482 &mut self,
483 sp: Span,
484 msg: &str,
485 suggestions: impl Iterator<Item = String>,
486 applicability: Applicability,
487 ) -> &mut Self {
488 let mut suggestions: Vec<_> = suggestions.collect();
489 suggestions.sort();
490 let substitutions = suggestions
491 .into_iter()
492 .map(|snippet| Substitution { parts: vec![SubstitutionPart { snippet, span: sp }] })
493 .collect();
494 self.push_suggestion(CodeSuggestion {
495 substitutions,
496 msg: msg.to_owned(),
497 style: SuggestionStyle::ShowCode,
498 applicability,
499 tool_metadata: Default::default(),
500 });
501 self
502 }
503
504 /// Prints out a message with multiple suggested edits of the code.
505 /// See also [`Diagnostic::span_suggestion()`].
506 pub fn multipart_suggestions(
507 &mut self,
508 msg: &str,
509 suggestions: impl Iterator<Item = Vec<(Span, String)>>,
510 applicability: Applicability,
511 ) -> &mut Self {
512 self.push_suggestion(CodeSuggestion {
513 substitutions: suggestions
514 .map(|sugg| Substitution {
515 parts: sugg
516 .into_iter()
517 .map(|(span, snippet)| SubstitutionPart { snippet, span })
518 .collect(),
519 })
520 .collect(),
521 msg: msg.to_owned(),
522 style: SuggestionStyle::ShowCode,
523 applicability,
524 tool_metadata: Default::default(),
525 });
526 self
527 }
528 /// Prints out a message with a suggested edit of the code. If the suggestion is presented
529 /// inline, it will only show the message and not the suggestion.
530 ///
531 /// See `CodeSuggestion` for more information.
532 pub fn span_suggestion_short(
533 &mut self,
534 sp: Span,
535 msg: &str,
536 suggestion: String,
537 applicability: Applicability,
538 ) -> &mut Self {
539 self.span_suggestion_with_style(
540 sp,
541 msg,
542 suggestion,
543 applicability,
544 SuggestionStyle::HideCodeInline,
545 );
546 self
547 }
548
549 /// Prints out a message for a suggestion without showing the suggested code.
550 ///
551 /// This is intended to be used for suggestions that are obvious in what the changes need to
552 /// be from the message, showing the span label inline would be visually unpleasant
553 /// (marginally overlapping spans or multiline spans) and showing the snippet window wouldn't
554 /// improve understandability.
555 pub fn span_suggestion_hidden(
556 &mut self,
557 sp: Span,
558 msg: &str,
559 suggestion: String,
560 applicability: Applicability,
561 ) -> &mut Self {
562 self.span_suggestion_with_style(
563 sp,
564 msg,
565 suggestion,
566 applicability,
567 SuggestionStyle::HideCodeAlways,
568 );
569 self
570 }
571
572 /// Adds a suggestion to the JSON output that will not be shown in the CLI.
573 ///
574 /// This is intended to be used for suggestions that are *very* obvious in what the changes
575 /// need to be from the message, but we still want other tools to be able to apply them.
576 pub fn tool_only_span_suggestion(
577 &mut self,
578 sp: Span,
579 msg: &str,
580 suggestion: String,
581 applicability: Applicability,
582 ) -> &mut Self {
583 self.span_suggestion_with_style(
584 sp,
585 msg,
586 suggestion,
587 applicability,
588 SuggestionStyle::CompletelyHidden,
589 );
590 self
591 }
592
593 /// Adds a suggestion intended only for a tool. The intent is that the metadata encodes
594 /// the suggestion in a tool-specific way, as it may not even directly involve Rust code.
595 pub fn tool_only_suggestion_with_metadata(
596 &mut self,
597 msg: &str,
598 applicability: Applicability,
599 tool_metadata: Json,
600 ) {
601 self.push_suggestion(CodeSuggestion {
602 substitutions: vec![],
603 msg: msg.to_owned(),
604 style: SuggestionStyle::CompletelyHidden,
605 applicability,
606 tool_metadata: ToolMetadata::new(tool_metadata),
607 })
608 }
609
610 pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
611 self.span = sp.into();
612 if let Some(span) = self.span.primary_span() {
613 self.sort_span = span;
614 }
615 self
616 }
617
618 pub fn set_is_lint(&mut self) -> &mut Self {
619 self.is_lint = true;
620 self
621 }
622
623 pub fn code(&mut self, s: DiagnosticId) -> &mut Self {
624 self.code = Some(s);
625 self
626 }
627
628 pub fn clear_code(&mut self) -> &mut Self {
629 self.code = None;
630 self
631 }
632
633 pub fn get_code(&self) -> Option<DiagnosticId> {
634 self.code.clone()
635 }
636
637 crate fn set_primary_message<M: Into<String>>(&mut self, msg: M) -> &mut Self {
638 self.message[0] = (msg.into(), Style::NoStyle);
639 self
640 }
641
642 pub fn message(&self) -> String {
643 self.message.iter().map(|i| i.0.as_str()).collect::<String>()
644 }
645
646 pub fn styled_message(&self) -> &Vec<(String, Style)> {
647 &self.message
648 }
649
650 /// Convenience function for internal use, clients should use one of the
651 /// public methods above.
652 ///
653 /// Used by `proc_macro_server` for implementing `server::Diagnostic`.
654 pub fn sub(
655 &mut self,
656 level: Level,
657 message: &str,
658 span: MultiSpan,
659 render_span: Option<MultiSpan>,
660 ) {
661 let sub = SubDiagnostic {
662 level,
663 message: vec![(message.to_owned(), Style::NoStyle)],
664 span,
665 render_span,
666 };
667 self.children.push(sub);
668 }
669
670 /// Convenience function for internal use, clients should use one of the
671 /// public methods above.
672 fn sub_with_highlights(
673 &mut self,
674 level: Level,
675 message: Vec<(String, Style)>,
676 span: MultiSpan,
677 render_span: Option<MultiSpan>,
678 ) {
679 let sub = SubDiagnostic { level, message, span, render_span };
680 self.children.push(sub);
681 }
682
683 /// Fields used for Hash, and PartialEq trait
684 fn keys(
685 &self,
686 ) -> (
687 &Level,
688 &Vec<(String, Style)>,
689 &Option<DiagnosticId>,
690 &MultiSpan,
691 &Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
692 Option<&Vec<SubDiagnostic>>,
693 ) {
694 (
695 &self.level,
696 &self.message,
697 &self.code,
698 &self.span,
699 &self.suggestions,
700 (if self.is_lint { None } else { Some(&self.children) }),
701 )
702 }
703 }
704
705 impl Hash for Diagnostic {
706 fn hash<H>(&self, state: &mut H)
707 where
708 H: Hasher,
709 {
710 self.keys().hash(state);
711 }
712 }
713
714 impl PartialEq for Diagnostic {
715 fn eq(&self, other: &Self) -> bool {
716 self.keys() == other.keys()
717 }
718 }
719
720 impl SubDiagnostic {
721 pub fn message(&self) -> String {
722 self.message.iter().map(|i| i.0.as_str()).collect::<String>()
723 }
724
725 pub fn styled_message(&self) -> &Vec<(String, Style)> {
726 &self.message
727 }
728 }