1use crate::{
2 diagnostic::{Code, Diagnostic, Hint, Issue, Reason, ToDiagnostic},
3 formatting::Indent,
4};
5
6use core::fmt;
7
8use either::Either;
9
10use sway_types::{Ident, IdentUnique, SourceId, Span, Spanned};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct CompileWarning {
16 pub span: Span,
17 pub warning_content: Warning,
18}
19
20impl Spanned for CompileWarning {
21 fn span(&self) -> Span {
22 self.span.clone()
23 }
24}
25
26impl CompileWarning {
27 pub fn to_friendly_warning_string(&self) -> String {
28 self.warning_content.to_string()
29 }
30
31 pub fn source_id(&self) -> Option<SourceId> {
32 self.span.source_id().cloned()
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub enum Warning {
38 NonClassCaseStructName {
39 struct_name: Ident,
40 },
41 NonClassCaseTypeParameter {
42 name: Ident,
43 },
44 NonClassCaseTraitName {
45 name: Ident,
46 },
47 NonClassCaseEnumName {
48 enum_name: Ident,
49 },
50 NonClassCaseEnumVariantName {
51 variant_name: Ident,
52 },
53 NonSnakeCaseStructFieldName {
54 field_name: Ident,
55 },
56 NonSnakeCaseFunctionName {
57 name: Ident,
58 },
59 NonScreamingSnakeCaseConstName {
60 name: Ident,
61 },
62 UnusedReturnValue {
63 r#type: String,
64 },
65 SimilarMethodFound {
66 lib: Ident,
67 module: Ident,
68 name: Ident,
69 },
70 ShadowsOtherSymbol {
71 name: Ident,
72 },
73 AsmBlockIsEmpty,
74 UninitializedAsmRegShadowsItem {
75 constant_or_configurable_or_variable: &'static str,
78 item: IdentUnique,
81 },
82 OverridingTraitImplementation,
83 DeadDeclaration,
84 DeadEnumDeclaration,
85 DeadFunctionDeclaration,
86 DeadStructDeclaration,
87 DeadTrait,
88 UnreachableCode,
89 DeadEnumVariant {
90 variant_name: Ident,
91 },
92 DeadMethod,
93 StructFieldNeverRead,
94 ShadowingReservedRegister {
95 reg_name: Ident,
96 },
97 DeadStorageDeclaration,
98 DeadStorageDeclarationForFunction {
99 unneeded_attrib: String,
100 },
101 MatchExpressionUnreachableArm {
102 match_value: Span,
103 match_type: String,
104 preceding_arms: Either<Vec<Span>, Span>,
106 unreachable_arm: Span,
107 is_last_arm: bool,
108 is_catch_all_arm: bool,
109 },
110 UnrecognizedAttribute {
111 attrib_name: Ident,
112 },
113 AttributeExpectedNumberOfArguments {
114 attrib_name: Ident,
115 received_args: usize,
116 expected_min_len: usize,
117 expected_max_len: Option<usize>,
118 },
119 UnexpectedAttributeArgumentValue {
120 attrib_name: Ident,
121 received_value: String,
122 expected_values: Vec<String>,
123 },
124 EffectAfterInteraction {
125 effect: String,
126 effect_in_suggestion: String,
127 block_name: Ident,
128 },
129 ModulePrivacyDisabled,
130 UsingDeprecated {
131 message: String,
132 },
133 DuplicatedStorageKey {
134 first_field: IdentUnique,
135 first_field_full_name: String,
136 first_field_key_is_compiler_generated: bool,
137 second_field: IdentUnique,
138 second_field_full_name: String,
139 second_field_key_is_compiler_generated: bool,
140 key: String,
141 experimental_storage_domains: bool,
143 },
144}
145
146impl fmt::Display for Warning {
147 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149 use sway_types::style::*;
150 use Warning::*;
151 match self {
152 NonClassCaseStructName { struct_name } => {
153 write!(f,
154 "Struct name \"{}\" is not idiomatic. Structs should have a ClassCase name, like \
155 \"{}\".",
156 struct_name,
157 to_upper_camel_case(struct_name.as_str())
158 )
159 }
160 NonClassCaseTypeParameter { name } => {
161 write!(f,
162 "Type parameter \"{}\" is not idiomatic. TypeParameters should have a ClassCase name, like \
163 \"{}\".",
164 name,
165 to_upper_camel_case(name.as_str())
166 )
167 }
168 NonClassCaseTraitName { name } => {
169 write!(f,
170 "Trait name \"{}\" is not idiomatic. Traits should have a ClassCase name, like \
171 \"{}\".",
172 name,
173 to_upper_camel_case(name.as_str())
174 )
175 }
176 NonClassCaseEnumName { enum_name } => write!(
177 f,
178 "Enum \"{}\"'s capitalization is not idiomatic. Enums should have a ClassCase \
179 name, like \"{}\".",
180 enum_name,
181 to_upper_camel_case(enum_name.as_str())
182 ),
183 NonSnakeCaseStructFieldName { field_name } => write!(
184 f,
185 "Struct field name \"{}\" is not idiomatic. Struct field names should have a \
186 snake_case name, like \"{}\".",
187 field_name,
188 to_snake_case(field_name.as_str())
189 ),
190 NonClassCaseEnumVariantName { variant_name } => write!(
191 f,
192 "Enum variant name \"{}\" is not idiomatic. Enum variant names should be \
193 ClassCase, like \"{}\".",
194 variant_name,
195 to_upper_camel_case(variant_name.as_str())
196 ),
197 NonSnakeCaseFunctionName { name } => {
198 write!(f,
199 "Function name \"{}\" is not idiomatic. Function names should be snake_case, like \
200 \"{}\".",
201 name,
202 to_snake_case(name.as_str())
203 )
204 }
205 NonScreamingSnakeCaseConstName { name } => {
206 write!(
207 f,
208 "Constant name \"{name}\" is not idiomatic. Constant names should be SCREAMING_SNAKE_CASE, like \
209 \"{}\".",
210 to_screaming_snake_case(name.as_str()),
211 )
212 },
213 UnusedReturnValue { r#type } => write!(
214 f,
215 "This returns a value of type {type}, which is not assigned to anything and is \
216 ignored."
217 ),
218 SimilarMethodFound { lib, module, name } => write!(
219 f,
220 "A method with the same name was found for type {name} in dependency \"{lib}::{module}\". \
221 Traits must be in scope in order to access their methods. "
222 ),
223 ShadowsOtherSymbol { name } => write!(
224 f,
225 "This shadows another symbol in this scope with the same name \"{name}\"."
226 ),
227 AsmBlockIsEmpty => write!(
228 f,
229 "This ASM block is empty."
230 ),
231 UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => write!(
232 f,
233 "This uninitialized register is shadowing a {}. You probably meant to also initialize it, like \"{item}: {item}\".",
234 constant_or_configurable_or_variable.to_ascii_lowercase(),
235 ),
236 OverridingTraitImplementation => write!(
237 f,
238 "This trait implementation overrides another one that was previously defined."
239 ),
240 DeadDeclaration => write!(f, "This declaration is never used."),
241 DeadEnumDeclaration => write!(f, "This enum is never used."),
242 DeadStructDeclaration => write!(f, "This struct is never used."),
243 DeadFunctionDeclaration => write!(f, "This function is never called."),
244 UnreachableCode => write!(f, "This code is unreachable."),
245 DeadEnumVariant { variant_name } => {
246 write!(f, "Enum variant {variant_name} is never constructed.")
247 }
248 DeadTrait => write!(f, "This trait is never implemented."),
249 DeadMethod => write!(f, "This method is never called."),
250 StructFieldNeverRead => write!(f, "This struct field is never accessed."),
251 ShadowingReservedRegister { reg_name } => write!(
252 f,
253 "This register declaration shadows the reserved register, \"{reg_name}\"."
254 ),
255 DeadStorageDeclaration => write!(
256 f,
257 "This storage declaration is never accessed and can be removed."
258 ),
259 DeadStorageDeclarationForFunction { unneeded_attrib } => write!(
260 f,
261 "This function's storage attributes declaration does not match its \
262 actual storage access pattern: '{unneeded_attrib}' attribute(s) can be removed."
263 ),
264 MatchExpressionUnreachableArm { .. } => write!(f, "This match arm is unreachable."),
265 UnrecognizedAttribute {attrib_name} => write!(f, "Unknown attribute: \"{attrib_name}\"."),
266 AttributeExpectedNumberOfArguments {attrib_name, received_args, expected_min_len, expected_max_len } => write!(
267 f,
268 "Attribute: \"{attrib_name}\" expected {} argument(s) received {received_args}.",
269 if let Some(expected_max_len) = expected_max_len {
270 if expected_min_len == expected_max_len {
271 format!("exactly {expected_min_len}")
272 } else {
273 format!("between {expected_min_len} and {expected_max_len}")
274 }
275 } else {
276 format!("at least {expected_min_len}")
277 }
278 ),
279 UnexpectedAttributeArgumentValue {attrib_name, received_value, expected_values } => write!(
280 f,
281 "Unexpected attribute value: \"{received_value}\" for attribute: \"{attrib_name}\" expected value {}",
282 expected_values.iter().map(|v| format!("\"{v}\"")).collect::<Vec<_>>().join(" or ")
283 ),
284 EffectAfterInteraction {effect, effect_in_suggestion, block_name} =>
285 write!(f, "{effect} after external contract interaction in function or method \"{block_name}\". \
286 Consider {effect_in_suggestion} before calling another contract"),
287 ModulePrivacyDisabled => write!(f, "Module privacy rules will soon change to make modules private by default.
288 You can enable the new behavior with the --experimental-private-modules flag, which will become the default behavior in a later release.
289 More details are available in the related RFC: https://github.com/FuelLabs/sway-rfcs/blob/master/rfcs/0008-private-modules.md"),
290 UsingDeprecated { message } => write!(f, "{}", message),
291 DuplicatedStorageKey { first_field_full_name, second_field_full_name, key, .. } =>
292 write!(f, "Two storage fields have the same storage key.\nFirst field: {first_field_full_name}\nSecond field: {second_field_full_name}\nKey: {key}"),
293 }
294 }
295}
296
297#[allow(dead_code)]
298const FUTURE_HARD_ERROR_HELP: &str =
299 "In future versions of Sway this warning will become a hard error.";
300
301impl ToDiagnostic for CompileWarning {
302 fn to_diagnostic(&self, source_engine: &sway_types::SourceEngine) -> Diagnostic {
303 let code = Code::warnings;
304 use sway_types::style::*;
305 use Warning::*;
306 match &self.warning_content {
307 NonScreamingSnakeCaseConstName { name } => Diagnostic {
308 reason: Some(Reason::new(code(1), "Constant name is not idiomatic".to_string())),
309 issue: Issue::warning(
310 source_engine,
311 name.span(),
312 format!("Constant \"{name}\" should be SCREAMING_SNAKE_CASE."),
313 ),
314 hints: vec![
315 Hint::help(
316 source_engine,
317 name.span(),
318 format!("Consider renaming it to, e.g., \"{}\".", to_screaming_snake_case(name.as_str())),
319 ),
320 ],
321 help: vec![
322 format!("In Sway, ABIs, structs, traits, and enums are CapitalCase."),
323 format!("Modules, variables, and functions are snake_case, while constants are SCREAMING_SNAKE_CASE."),
324 ],
325 },
326 MatchExpressionUnreachableArm { match_value, match_type, preceding_arms, unreachable_arm, is_last_arm, is_catch_all_arm } => Diagnostic {
327 reason: Some(Reason::new(code(1), "Match arm is unreachable".to_string())),
328 issue: Issue::warning(
329 source_engine,
330 unreachable_arm.clone(),
331 match (*is_last_arm, *is_catch_all_arm) {
332 (true, true) => format!("Last catch-all match arm `{}` is unreachable.", unreachable_arm.as_str()),
333 _ => format!("Match arm `{}` is unreachable.", unreachable_arm.as_str())
334 }
335 ),
336 hints: vec![
337 Hint::info(
338 source_engine,
339 match_value.clone(),
340 format!("The expression to match on is of type \"{match_type}\".")
341 ),
342 if preceding_arms.is_right() {
343 Hint::help(
344 source_engine,
345 preceding_arms.as_ref().unwrap_right().clone(),
346 format!("Catch-all arm `{}` makes all match arms below it unreachable.", preceding_arms.as_ref().unwrap_right().as_str())
347 )
348 }
349 else {
350 Hint::info(
351 source_engine,
352 Span::join_all(preceding_arms.as_ref().unwrap_left().clone()),
353 if *is_last_arm {
354 format!("Preceding match arms already match all possible values of `{}`.", match_value.as_str())
355 }
356 else {
357 format!("Preceding match arms already match all the values that `{}` can match.", unreachable_arm.as_str())
358 }
359 )
360 }
361 ],
362 help: if preceding_arms.is_right() {
363 let catch_all_arm = preceding_arms.as_ref().unwrap_right().as_str();
364 vec![
365 format!("Catch-all patterns make sense only in last match arms."),
366 format!("Consider removing the catch-all arm `{catch_all_arm}` or making it the last arm."),
367 format!("Consider removing the unreachable arms below the `{catch_all_arm}` arm."),
368 ]
369 }
370 else if *is_last_arm && *is_catch_all_arm {
371 vec![
372 format!("Catch-all patterns are often used in last match arms."),
373 format!("But in this case, the preceding arms already match all possible values of `{}`.", match_value.as_str()),
374 format!("Consider removing the unreachable last catch-all arm."),
375 ]
376 }
377 else {
378 vec![
379 format!("Consider removing the unreachable arm."),
380 ]
381 }
382 },
383 UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => Diagnostic {
384 reason: Some(Reason::new(code(1), format!("Uninitialized ASM register is shadowing a {}", constant_or_configurable_or_variable.to_ascii_lowercase()))),
385 issue: Issue::warning(
386 source_engine,
387 self.span(),
388 format!("Uninitialized register \"{item}\" is shadowing a {} of the same name.", constant_or_configurable_or_variable.to_ascii_lowercase()),
389 ),
390 hints: {
391 let mut hints = vec![
392 Hint::info(
393 source_engine,
394 item.span(),
395 format!("{constant_or_configurable_or_variable} \"{item}\" is declared here.")
396 ),
397 ];
398
399 hints.append(&mut Hint::multi_help(
400 source_engine,
401 &self.span(),
402 vec![
403 format!("Are you trying to initialize the register to the value of the {}?", constant_or_configurable_or_variable.to_ascii_lowercase()),
404 format!("In that case, you must do it explicitly: `{item}: {item}`."),
405 format!("Otherwise, to avoid the confusion with the shadowed {}, consider renaming the register \"{item}\".", constant_or_configurable_or_variable.to_ascii_lowercase()),
406 ]
407 ));
408
409 hints
410 },
411 help: vec![],
412 },
413 AsmBlockIsEmpty => Diagnostic {
414 reason: Some(Reason::new(code(1), "ASM block is empty".to_string())),
415 issue: Issue::warning(
416 source_engine,
417 self.span(),
418 "This ASM block is empty.".to_string(),
419 ),
420 hints: vec![],
421 help: vec![
422 "Consider adding assembly instructions or a return register to the ASM block, or removing the block altogether.".to_string(),
423 ],
424 },
425 DuplicatedStorageKey { first_field, first_field_full_name, first_field_key_is_compiler_generated, second_field, second_field_full_name, second_field_key_is_compiler_generated, key, experimental_storage_domains } => Diagnostic {
426 reason: Some(Reason::new(code(1), "Two storage fields have the same storage key".to_string())),
427 issue: Issue::warning(
428 source_engine,
429 first_field.span(),
430 format!("\"{first_field_full_name}\" has the same storage key as \"{second_field_full_name}\"."),
431 ),
432 hints: vec![
433 Hint::info(
434 source_engine,
435 second_field.span(),
436 format!("\"{second_field_full_name}\" is declared here."),
437 ),
438 ],
439 help: vec![
440 if *first_field_key_is_compiler_generated || *second_field_key_is_compiler_generated {
441 format!("The key of \"{}\" is generated by the compiler using the following formula:",
442 if *first_field_key_is_compiler_generated {
443 first_field_full_name
444 } else {
445 second_field_full_name
446 }
447 )
448 } else {
449 "Both keys are explicitly defined by using the `in` keyword.".to_string()
450 },
451 if *first_field_key_is_compiler_generated || *second_field_key_is_compiler_generated {
452 if *experimental_storage_domains {
453 format!("{}sha256((0u8, \"{}\"))",
454 Indent::Single,
455 if *first_field_key_is_compiler_generated {
456 first_field_full_name
457 } else {
458 second_field_full_name
459 }
460 )
461 } else {
462 format!("{}sha256(\"{}\")",
463 Indent::Single,
464 if *first_field_key_is_compiler_generated {
465 first_field_full_name
466 } else {
467 second_field_full_name
468 }
469 )
470 }
471 } else {
472 Diagnostic::help_none()
473 },
474 format!("The common key is: {key}.")
475 ],
476 },
477 _ => Diagnostic {
478 issue: Issue::warning(source_engine, self.span(), format!("{}", self.warning_content)),
483 ..Default::default()
484 }
485 }
486 }
487}
488
489#[cfg(test)]
490mod test {
491 use sway_types::style::*;
492
493 #[test]
494 fn detect_styles() {
495 let snake_cases = [
496 "hello",
497 "__hello",
498 "blah32",
499 "some_words_here",
500 "___some_words_here",
501 ];
502 let screaming_snake_cases = ["SOME_WORDS_HERE", "___SOME_WORDS_HERE"];
503 let upper_camel_cases = [
504 "Hello",
505 "__Hello",
506 "Blah32",
507 "SomeWordsHere",
508 "___SomeWordsHere",
509 ];
510 let screaming_snake_case_or_upper_camel_case_idents = ["HELLO", "__HELLO", "BLAH32"];
511 let styleless_idents = ["Mix_Of_Things", "__Mix_Of_Things", "FooBar_123"];
512 for name in &snake_cases {
513 assert!(is_snake_case(name));
514 assert!(!is_screaming_snake_case(name));
515 assert!(!is_upper_camel_case(name));
516 }
517 for name in &screaming_snake_cases {
518 assert!(!is_snake_case(name));
519 assert!(is_screaming_snake_case(name));
520 assert!(!is_upper_camel_case(name));
521 }
522 for name in &upper_camel_cases {
523 assert!(!is_snake_case(name));
524 assert!(!is_screaming_snake_case(name));
525 assert!(is_upper_camel_case(name));
526 }
527 for name in &screaming_snake_case_or_upper_camel_case_idents {
528 assert!(!is_snake_case(name));
529 assert!(is_screaming_snake_case(name));
530 assert!(is_upper_camel_case(name));
531 }
532 for name in &styleless_idents {
533 assert!(!is_snake_case(name));
534 assert!(!is_screaming_snake_case(name));
535 assert!(!is_upper_camel_case(name));
536 }
537 }
538
539 #[test]
540 fn convert_to_snake_case() {
541 assert_eq!("hello", to_snake_case("HELLO"));
542 assert_eq!("___hello", to_snake_case("___HELLO"));
543 assert_eq!("blah32", to_snake_case("BLAH32"));
544 assert_eq!("some_words_here", to_snake_case("SOME_WORDS_HERE"));
545 assert_eq!("___some_words_here", to_snake_case("___SOME_WORDS_HERE"));
546 assert_eq!("hello", to_snake_case("Hello"));
547 assert_eq!("___hello", to_snake_case("___Hello"));
548 assert_eq!("blah32", to_snake_case("Blah32"));
549 assert_eq!("some_words_here", to_snake_case("SomeWordsHere"));
550 assert_eq!("___some_words_here", to_snake_case("___SomeWordsHere"));
551 assert_eq!("some_words_here", to_snake_case("someWordsHere"));
552 assert_eq!("___some_words_here", to_snake_case("___someWordsHere"));
553 assert_eq!("mix_of_things", to_snake_case("Mix_Of_Things"));
554 assert_eq!("__mix_of_things", to_snake_case("__Mix_Of_Things"));
555 assert_eq!("foo_bar_123", to_snake_case("FooBar_123"));
556 }
557
558 #[test]
559 fn convert_to_screaming_snake_case() {
560 assert_eq!("HELLO", to_screaming_snake_case("hello"));
561 assert_eq!("___HELLO", to_screaming_snake_case("___hello"));
562 assert_eq!("BLAH32", to_screaming_snake_case("blah32"));
563 assert_eq!(
564 "SOME_WORDS_HERE",
565 to_screaming_snake_case("some_words_here")
566 );
567 assert_eq!(
568 "___SOME_WORDS_HERE",
569 to_screaming_snake_case("___some_words_here")
570 );
571 assert_eq!("HELLO", to_screaming_snake_case("Hello"));
572 assert_eq!("___HELLO", to_screaming_snake_case("___Hello"));
573 assert_eq!("BLAH32", to_screaming_snake_case("Blah32"));
574 assert_eq!("SOME_WORDS_HERE", to_screaming_snake_case("SomeWordsHere"));
575 assert_eq!(
576 "___SOME_WORDS_HERE",
577 to_screaming_snake_case("___SomeWordsHere")
578 );
579 assert_eq!("SOME_WORDS_HERE", to_screaming_snake_case("someWordsHere"));
580 assert_eq!(
581 "___SOME_WORDS_HERE",
582 to_screaming_snake_case("___someWordsHere")
583 );
584 assert_eq!("MIX_OF_THINGS", to_screaming_snake_case("Mix_Of_Things"));
585 assert_eq!(
586 "__MIX_OF_THINGS",
587 to_screaming_snake_case("__Mix_Of_Things")
588 );
589 assert_eq!("FOO_BAR_123", to_screaming_snake_case("FooBar_123"));
590 }
591
592 #[test]
593 fn convert_to_upper_camel_case() {
594 assert_eq!("Hello", to_upper_camel_case("hello"));
595 assert_eq!("___Hello", to_upper_camel_case("___hello"));
596 assert_eq!("Blah32", to_upper_camel_case("blah32"));
597 assert_eq!("SomeWordsHere", to_upper_camel_case("some_words_here"));
598 assert_eq!(
599 "___SomeWordsHere",
600 to_upper_camel_case("___some_words_here")
601 );
602 assert_eq!("Hello", to_upper_camel_case("HELLO"));
603 assert_eq!("___Hello", to_upper_camel_case("___HELLO"));
604 assert_eq!("Blah32", to_upper_camel_case("BLAH32"));
605 assert_eq!("SomeWordsHere", to_upper_camel_case("SOME_WORDS_HERE"));
606 assert_eq!(
607 "___SomeWordsHere",
608 to_upper_camel_case("___SOME_WORDS_HERE")
609 );
610 assert_eq!("SomeWordsHere", to_upper_camel_case("someWordsHere"));
611 assert_eq!("___SomeWordsHere", to_upper_camel_case("___someWordsHere"));
612 assert_eq!("MixOfThings", to_upper_camel_case("Mix_Of_Things"));
613 assert_eq!("__MixOfThings", to_upper_camel_case("__Mix_Of_Things"));
614 assert_eq!("FooBar123", to_upper_camel_case("FooBar_123"));
615 }
616}