use std::collections::{HashMap, HashSet};
use std::fmt;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum DiagID {
WUnusedVariable = 1,
WUnusedParameter,
WUnusedFunction,
WUnusedLabel,
WUnusedValue,
WUnusedResult,
WUnusedLocalTypedef,
WUnusedPrivateField,
WUnusedLambdaCapture,
WUnusedConstVariable,
WUnusedButSetVariable,
WUnusedButSetParameter,
WUnusedMacro,
WUnusedMemberFunction,
WUnusedTemplate,
WUnusedExceptionParameter,
WUnusedVolatile,
WUnusedImport,
WSignCompare = 40,
WSignConversion,
WSignPromo,
WConversion = 50,
WConversionLoss,
WShorten64To32,
WBitFieldConversion,
WFloatConversion,
WImplicitIntConversion,
WImplicitFloatConversion,
WEnumConversion,
WNullableToNonNullable,
WFormat = 70,
WFormatSecurity,
WFormatExtraArgs,
WFormatZeroLength,
WFormatNonLiteral,
WFormatContainsNul,
WFormatInvalidSpecifier,
WFormatOverflow,
WFormatPedantic,
WFormatY2K,
WFormatNonStandard,
WShadow = 90,
WShadowField,
WShadowIvar,
WShadowAll,
WShadowUncapturedLocal,
WShadowFieldInConstructor,
WShadowFieldInConstructorModified,
WComma,
WParentheses = 110,
WParenthesesEquality,
WParenthesesBitwise,
WLogicalOpParentheses,
WShiftOpParentheses,
WOverloadedShiftOpParentheses,
WDanglingElse,
WSwitch = 130,
WSwitchEnum,
WSwitchDefault,
WSwitchBool,
WSwitchOutOfRange,
WSwitchUnreachableDefault,
WCoveredSwitchDefault,
WImplicitFallthrough = 150,
WImplicitFallthroughPerFunction,
WStrictAliasing = 160,
WStrictAliasing0,
WStrictAliasing1,
WStrictAliasing2,
WReturnType = 170,
WReturnStackAddress,
WReturnLocalAddr,
WReturnStdMove,
WReturnStdMoveInCXX11,
WUninitialized = 190,
WSometimesUninitialized,
WConditionalUninitialized,
WMaybeUninitialized,
WStaticSelfInit,
WUninitializedSelfReference,
WTautologicalCompare = 210,
WTautologicalConstantCompare,
WTautologicalPointerCompare,
WTautologicalOverlapCompare,
WTautologicalUndefinedCompare,
WTautologicalTypeLimitCompare,
WTautologicalBitwiseCompare,
WTautologicalOutOfRange,
WNullDereference = 230,
WDivisionByZero,
WArrayBounds,
WLoopAnalysis,
WStaticInInline,
WUnreachableCode,
WUnreachableCodeBreak,
WUnreachableCodeReturn,
WUnreachableCodeLoopIncrement,
WUnreachableCodeAggressive,
WCharSubscripts = 250,
WMainReturnType,
WMain,
WMissingBraces,
WMissingFieldInitializers,
WMissingVariableDeclarations,
WMissingPrototypes,
WMissingNoEscape,
WMissingMethodReturnType,
WMissingIncludeDirs,
WDeprecated = 270,
WDeprecatedDeclarations,
WDeprecatedCopy,
WDeprecatedDynamicExceptionSpec,
WDeprecatedRegister,
WDeprecatedWritableStrings,
WDeprecatedIncrementBool,
WDeprecatedObjCIsaUsage,
WDeprecatedObjCPointerIntrospect,
WDeprecatedObjCPointerIntrospectPerformSelector,
WDeprecatedImplementations,
WDeprecatedObjCExceptions,
WDocumentation = 290,
WDocumentationDeprecatedSync,
WDocumentationUnknownCommand,
WDocumentationUnclosedCommand,
WDocumentationHTML,
WDocumentationInLatexMath,
WDocumentationOuterMatching,
WExtra = 310,
WExtraSemi,
WExtraSemiStmt,
WExtraTokens,
WExtraQualification,
WExtraSemiAfterMemberFunction,
WOverloadedVirtual = 330,
WHiddenVirtual,
WAbstractFinalClass,
WAbstractVBaseForbidden,
WAbstractReturnType,
WInaccessibleBase,
WDelegatingCtorCycles,
WReorder,
WReorderCtor,
WReorderInitList,
WInconsistentMissingOverride,
WInconsistentMissingDestructorOverride,
WSuggestOverride,
WSuggestDestructorOverride,
WFinalDtorNonFinalClass,
WReservedUserDefinedLiteral,
WReservedIdMacro,
WZeroAsNullPointerConstant,
WNullArithmetic,
WNullConversion,
WNullPointerArithmetic,
WUserDefinedLiterals,
WUserDefinedWarnings,
WAttributes = 370,
WUnknownAttributes,
WIgnoredAttributes,
WGnuAttribute,
WFourCharCharacterConstants,
WCxx14Compat,
WCxx17Compat,
WCxx20Compat,
WCxx23Compat,
WC99Compat,
WC11Compat,
WAll = 390,
WPedantic,
WPointerArith,
WPointerToIntCast,
WIntToPointerCast,
WCastAlign,
WCastQual,
WBadFunctionCast,
WFloatEqual,
WUndef,
WTrigraphs,
WComment,
WMultichar,
WCharLiteralAscii,
WLargeByValueCopy,
WMove,
WPessimizingMove,
WRedundantMove,
WSelfMove,
WSelfAssign,
WSelfAssignOverloaded,
WSelfAssignField,
WEnumCompareCondition,
WDuplicateDeclSpecifier,
WDuplicateMethodSpec,
WDuplicateProtocol,
WDuplicateEnumValues,
WEmptyBody,
WEmptyTranslationUnit,
WExitTimeDestructors,
WGlobalConstructors,
WExternCCompat,
WExternInitializer,
WHeaderHygiene,
WPragmaPack,
WPragmaPackSuspiciousInclude,
WPragmaOnceOutOfHeader,
WNewlineEOF,
WEndifLabels,
WImplicitInt,
WImplicitFunctionDeclarations,
WIntConversion,
WIntInBoolContext,
WLogicalNotParentheses,
WLogicalOpParenthesesRHS,
WLongLong,
WNestedExterns,
WNoNewlineEOF,
WOldStyleCast,
WOldStyleDefinition,
WOverlengthStrings,
WPointerSign,
WRedundantDecls,
WReturnTypeCVQual,
WSequencePoint,
WShiftCountOverflow,
WShiftCountNegative,
WShiftNegativeValue,
WStringPlusInt,
WStringPlusChar,
WStringConversion,
WSyncFetchAndNand,
WTypeLimits,
WUnreachableCodeAggressiveOpt,
WUnsequenced,
WVarargs,
WVecEltSize,
WVexingParse,
WVLA,
WVolatileRegisterVar,
WWriteStrings,
WZeroLengthArray,
WClangSpecific = 500,
WArcBridgeCastsDisallowedInNonARC,
WArcRepeatedUseOfWeak,
WArcRetainCycles,
WArcUnsafeRetainedAssign,
WBlockCaptureAutoReleasing,
WBlockCaptureSelf,
WCFStringLiteral,
WObjCRootClass,
WObjCForwardClassRealization,
WObjCInterfaceIvars,
WObjCLiteralConversion,
WObjCMissingSuperCalls,
WObjCMultipleMethodNames,
WObjCPropertyImplementation,
WObjCPropertyNoAttribute,
WObjCReadonlyPropertyHasSetter,
WObjCStringComparison,
WObjCStringFormatting,
WObjCSuperClassLayout,
WSelector,
WStrictSelectorMatch,
WUndeclaredSelector,
WUnknownPragmas,
WUnknownWarningOption,
WVariadicMacros,
WObjCDuplicateCategory,
WObjCDesignatedInit,
WObjCPropertySynthesis,
WObjCPropertyAssignOnObject,
WObjCPropertyRetainOrCopy,
WObjCBoxing,
WMost = 560,
WExtraSemiBeforeMemberBody,
WInfiniteRecursion,
WSuspiciousBzero,
WSuspiciousMemaccess,
WNonPODVarargs,
WStringCompression,
WEnumTooLarge,
WExcessInitializers,
WFloatOverflowConversion,
WFloatZeroConversion,
WImplicitFallthroughPerFunctionWithIntended,
WMismatchedNewDelete,
WMismatchedReturnTypes,
WMismatchedTags,
WNonLiteralNullConversion,
WNonVirtualDtor,
WNullDereferenceWithOffset,
WObjCBoxingAutorelease,
WObjCDictionaryLiteral,
WObjCImplicitRetainSelf,
WOverridingTAndH,
WRangedLoopAnalysis,
WSection,
WSentinel,
WStringConcat,
WStringLiteralConversion,
WStructPadding,
WThreadSafety,
WThreadSafetyAnalysis,
WTrampoline,
WTrivialAutoVarInit,
WUnavailableDeclarations,
WUnguardedAvailability,
WUnguardedAvailabilityNew,
WUnknownSanitizers,
WUnsupportedTargetOpt,
WUnusedComparison,
WUnusedFileScoped,
WVariadicMacroArguments,
ErrExpectedSemiAfterExpr = 1000,
ErrExpectedSemiAfterDecl,
ErrExpectedExpression,
ErrExpectedType,
ErrExpectedIdentifier,
ErrExpectedRBrace,
ErrExpectedRParen,
ErrExpectedRBracket,
ErrExpectedComma,
ErrExpectedColon,
ErrExpectedFuncBody,
ErrExpectedStatement,
ErrExpectedDeclaration,
ErrExpectedParameter,
ErrExpectedTemplateArg,
ErrExpectedQualifiedName,
ErrExpectedNamespaceName,
ErrExpectedClassName,
ErrExpectedEnumName,
ErrExpectedConstantExpression,
ErrExpectedStringLiteral,
ErrExpectedIntegerLiteral,
ErrExpectedFloatingLiteral,
ErrExpectedCharLiteral,
ErrExpectedBooleanLiteral,
ErrExpectedNullPtr,
ErrExpectedThis,
ErrExpectedBaseSpecifier,
ErrExpectedMemberName,
ErrExpectedInitializer,
ErrExpectedEqual,
ErrExpectedArrow,
ErrExpectedDot,
ErrExpectedStar,
ErrExpectedAmpersand,
ErrExpectedOperator,
ErrExpectedLoopBody,
ErrExpectedSwitchBody,
ErrExpectedCaseOrDefault,
ErrExpectedCatch,
ErrUndeclaredIdentifier = 1050,
ErrUndeclaredVarUse,
ErrUndeclaredFunction,
ErrUndeclaredType,
ErrUndeclaredLabel,
ErrUndeclaredNamespace,
ErrUndeclaredTemplate,
ErrRedefinition,
ErrRedefinitionDifferentType,
ErrRedefinitionOfLabel,
ErrRedefinitionOfEnumerator,
ErrRedefinitionOfTypedef,
ErrRedefinitionOfModule,
ErrRedeclaration,
ErrRedeclarationDifferentKind,
ErrConflictTypes,
ErrAmbiguousReference,
ErrAmbiguousOverload,
ErrAmbiguousConversion,
ErrAmbiguousBase,
ErrCannotInitialize = 1080,
ErrCannotInitializeWithInitList,
ErrCannotConvert,
ErrCannotConvertToPointer,
ErrCannotConvertToReference,
ErrCannotConvertFunctionToPointer,
ErrCannotConvertVectorType,
ErrCannotBindBitfield,
ErrCannotBindToTemporary,
ErrCannotPassNonTriviallyCopyable,
ErrCannotCatchType,
ErrCannotThrowType,
ErrIncompatibleTypes,
ErrIncompatiblePointerTypes,
ErrIncompatibleIntegerConversion,
ErrIncompatiblePointerToInteger,
ErrIncompatibleOperandTypes,
ErrIncompatibleVectorTypes,
ErrIncompatibleBlockPointer,
ErrNoMatchingFunction = 1110,
ErrNoMatchingMemberFunction,
ErrNoMatchingConstructor,
ErrNoMatchingConversion,
ErrTooManyArgs,
ErrTooFewArgs,
ErrCallToDeleted,
ErrCallToInaccessible,
ErrCallToPureVirtual,
ErrCallToNonCallable,
ErrCallToNonFunction,
ErrDefaultArgMismatch,
ErrTemplateArgDeductionFailed,
ErrCannotDeduceTemplateArgs,
ErrAmbiguousTemplateArgs,
ErrNoViableConversion,
ErrInvalidExplicitSpecifier,
ErrRecursiveTemplateInstantiation,
ErrUnaryAmpersandOnTemporary = 1140,
ErrUnaryAmpersandOnBitfield,
ErrUnaryAmpersandOnRegister,
ErrDivisionByZeroConst,
ErrShiftByNegative,
ErrShiftByExcess,
ErrModuloByZero,
ErrAssignmentToConst,
ErrAssignmentToArray,
ErrAssignmentToFunction,
ErrIncrementOfBoolean,
ErrIncrementOfEnum,
ErrIncrementOfReadOnly,
ErrDecrementOfBoolean,
ErrArithmeticOnVoidPtr,
ErrArithmeticOnFunctionPtr,
ErrSubscriptOnArray,
ErrSubscriptOnPointer,
ErrSubscriptOnNonArray,
ErrMemberRefOnNonStruct,
ErrMemberRefOnNonClass,
ErrAbstractClass = 1170,
ErrAbstractDecl,
ErrPureVirtualCall,
ErrPrivateMember,
ErrProtectedMember,
ErrInaccessibleBase,
ErrAmbiguousBaseClass,
ErrVirtualBaseClass,
ErrOverrideMismatch,
ErrFinalOverride,
ErrMissingVtable,
ErrMissingTypeinfo,
ErrInvalidUseOfThis,
ErrInvalidUseOfMember,
ErrInvalidUseOfTemplate,
ErrInvalidUseOfAuto,
ErrInvalidUseOfDecltype,
ErrInvalidUseOfVoid,
ErrInvalidUseOfIncompleteType,
ErrFieldOfIncompleteType,
ErrSizeOfIncompleteType,
ErrAlignOfIncompleteType,
ErrDeleteOfIncompleteType,
ErrDeleteOfAbstractClass,
ErrNewOfAbstractClass,
ErrThrowOfIncompleteType,
ErrCatchOfIncompleteType,
ErrReturnOfIncompleteType,
ErrParamOfIncompleteType,
ErrPPHashError = 1200,
ErrPPExpectedNewline,
ErrPPExpectedIdentifier,
ErrPPExpectedValue,
ErrPPExpectedInclude,
ErrPPExpectedConditional,
ErrPPExpectedEndif,
ErrPPIncludeNotFound,
ErrPPIncludeTooDeep,
ErrPPMacroRedefined,
ErrPPMacroNotDefined,
ErrPPMacroExpansionTooDeep,
ErrPPInvalidToken,
ErrPPUnterminatedComment,
ErrPPUnterminatedString,
ErrPPUnterminatedChar,
ErrPPDirectiveInMacroArg,
ErrPPEmptyFileName,
ErrPPInvalidDirective,
ErrPPNestedComment,
ErrPPBadPaste,
ErrPPBadStringify,
ErrPPVariadicMacro,
ErrPPExpectedFeatureName,
ErrPPDefinedInMacro,
ErrPPLineDirective,
ErrPPPragmaExpected,
ErrPPPragmaUnsupported,
ErrPPMissingEndif,
ErrPPExtraEndif,
ErrPPElseAfterElse,
ErrPPElifAfterElse,
ErrPPNonPortablePath,
ErrPPIncludeNextOutsideHeader,
ErrPPImportInMacro,
ErrStaticAssert = 1240,
ErrStaticAssertFailed,
ErrInvalidArraySize,
ErrNegativeArraySize,
ErrZeroSizeArray,
ErrFlexibleArrayMember,
ErrFlexibleArrayInUnion,
ErrFlexibleArrayInEmptyStruct,
ErrConstexprBodyNoReturn,
ErrConstexprFunctionNeverConstant,
ErrConstexprVarRequiresInit,
ErrConstexprVarRequiresConstInit,
ErrDuplicateMember,
ErrDuplicateBase,
ErrDuplicateCase,
ErrDuplicateDefault,
ErrDuplicateAttribute,
ErrDuplicateAttributeArg,
ErrMultipleStorageClasses,
ErrStorageClassForFunction,
ErrTypeSpecifierRepeated,
ErrIllegalStorageClassOnMember,
ErrThreadNonStatic,
ErrThreadLocalInPlainObject,
ErrAtomicSpecifierBadType,
ErrAlignasOnBitField,
ErrAlignasUnderaligned,
NotePreviousDefinition = 2000,
NotePreviousDeclaration,
NotePreviousImplicitDeclaration,
NotePreviousBuiltinDeclaration,
NotePreviousUse,
NotePreviousIf,
NotePreviousElse,
NoteMatching,
NoteCandidateFunction,
NoteCandidateConstructor,
NoteCandidateTemplate,
NoteCandidateOperator,
NoteTemplateParamDeclaredHere,
NoteDefaultArgInstantiatedHere,
NoteInInstantiationOf,
NoteInExpansionOf,
NoteInSubstitutionOf,
NoteInDefinitionOf,
NoteInMemberFunction,
NoteInLambdaFunction,
NoteWhileDeducingTemplateArgs,
NoteDuringTemplateArgDeduction,
NoteForwardDeclaration,
NoteForwardDeclarationOfClass,
NoteForwardDeclarationOfEnum,
NoteDeclarationHere,
NoteDefinitionHere,
NoteHeaderHere,
NoteIncludeLocation,
NotePreviousInclude,
NoteMacroDefinedHere,
NoteMacroExpandedHere,
NotePragmaHere,
NoteUninitializedHere,
NoteAssignedHere,
NoteConditionHere,
NoteLoopHere,
NoteExceptionHere,
NoteConversionHere,
NoteOverloadResolutionHere,
NoteTypeMismatch,
NoteValueHere,
NoteParameterHere,
NoteReturnValueHere,
NoteDestructorHere,
NoteConstructorHere,
NoteBaseClassHere,
NoteMemberDeclaredHere,
NoteLambdaCaptureHere,
NoteLambdaHere,
NoteTemplateHere,
NoteSpecializationHere,
NoteExplicitInstantiationHere,
NotePartialSpecializationHere,
NoteDefaultTemplateArgHere,
NoteDeducedTypeHere,
NoteWhileBuildingDeductionGuide,
NoteShadowDeclHere,
NotePreviousShadowDecl,
NoteFieldDeclaredHere,
NoteBitFieldDeclaredHere,
NoteAnonymousStructHere,
NoteAnonymousUnionHere,
NoteEnumDeclaredHere,
NoteTypedefHere,
NoteUsingDeclHere,
NoteNamespaceHere,
NoteModuleHere,
NoteImportHere,
NoteExportHere,
NoteInEvaluatingExpr,
NoteInEvaluatingConstraint,
NoteInConstraintNormalization,
NoteWhileCheckingConstraintSatisfaction,
NoteConceptHere,
NoteRequirementHere,
NoteReturnTypeHere,
NoteCallableHere,
NoteInstantiatedFrom,
NoteExpandedFrom,
NoteInRecursiveInstantiation,
NoteArrayInitHere,
NoteDesignatedInitHere,
NoteAggregateInitHere,
NoteDefaultInitHere,
NoteValueInitHere,
NoteCopyInitHere,
NoteDirectInitHere,
NoteListInitHere,
NoteReferenceInitHere,
FatalNoInputFiles = 3000,
FatalCannotOpenFile,
FatalCannotWriteFile,
FatalCannotExecuteBinary,
FatalOutOfMemory,
FatalStackExhausted,
FatalIncludeTooDeep,
FatalMacroExpansionTooDeep,
FatalTemplateInstantiationTooDeep,
FatalRecursiveTemplateInstantiation,
FatalTooManyErrors,
FatalInvalidTarget,
FatalUnsupportedTarget,
FatalInvalidArch,
FatalInvalidCPU,
FatalInvalidFeature,
FatalInvalidOption,
FatalConflictingOptions,
FatalMissingArg,
FatalUnknownArg,
FatalInvalidValue,
FatalModuleBuildFailed,
FatalModuleFileOutOfDate,
FatalModuleFileInvalid,
FatalCyclicModuleDependency,
FatalPCHOutOfDate,
FatalPCHInvalid,
FatalPCHCXXMismatch,
FatalPCHTargetMismatch,
FatalPCHLanguageMismatch,
FatalPCHVersionMismatch,
FatalPCHHasDifferentModuleCache,
FatalCannotFindHeaders,
FatalCannotFindSDK,
FatalSDKTooOld,
FatalSDKMissingRequired,
FatalBrokenInstallation,
FatalInternalError,
FatalUnimplemented,
FatalUnreachable,
FatalLTOError,
FatalBackendError,
FatalCodeGenError,
FatalLinkError,
FatalProfileDataError,
FatalSanitizerError,
RemarkBackendOptimization = 4000,
RemarkLoopVectorized,
RemarkLoopInterleaved,
RemarkSLPVectorized,
RemarkInlinedFunction,
RemarkNotInlined,
RemarkLoopUnrolled,
RemarkLoopUnrolledPartial,
RemarkLoopUnrolledComplete,
RemarkLoopUnrolledFailed,
RemarkLoopFused,
RemarkLoopDistributed,
RemarkLoopInterchanged,
RemarkLoopUnswitched,
RemarkLoopVersioned,
RemarkLicmMoved,
RemarkGVNLoadEliminated,
RemarkMemCpyOptimized,
RemarkDivRemExpanded,
RemarkFloat2IntConverted,
RemarkConstantHoisted,
RemarkReassociated,
RemarkInstructionCount,
RemarkInlineCost,
RemarkFunctionCloned,
RemarkCallSiteInlined,
RemarkCallSiteNotInlined,
RemarkProfileDataApplied,
RemarkProfileDataMissing,
RemarkInstrProfApplied,
RemarkSampleProfileApplied,
RemarkAnnotationStackAccess,
RemarkAnnotationJumpTable,
RemarkAnnotationVectorWidth,
RemarkAnnotationInterleaveCount,
RemarkAnnotationUnrollCount,
RemarkAnnotationLoopDistribution,
RemarkSFINAEExplanation,
RemarkCandidateDisabled,
RemarkCandidateNotViable,
RemarkTemplateDeductionFailure,
RemarkOverloadCandidateDropped,
RemarkUsingShadowDecl,
RemarkModuleImport,
RemarkModuleExport,
RemarkModuleBuild,
RemarkPragmaWarning,
RemarkPragmaMessage,
RemarkBackendPlugin,
IgnoredMacroDefined = 5000,
IgnoredMacroUndefined,
IgnoredPragma,
IgnoredAttribute,
IgnoredExtension,
IgnoredWarning,
IgnoredDeprecated,
}
impl DiagID {
pub fn category(&self) -> &'static str {
let raw = *self as u32;
if raw < 1000 {
"Warning"
} else if raw < 2000 {
"Error"
} else if raw < 3000 {
"Note"
} else if raw < 4000 {
"Fatal"
} else if raw < 5000 {
"Remark"
} else {
"Ignored"
}
}
pub fn default_severity(&self) -> DiagSeverity {
let raw = *self as u32;
if raw < 1000 {
DiagSeverity::Warning
} else if raw < 2000 {
DiagSeverity::Error
} else if raw < 3000 {
DiagSeverity::Note
} else if raw < 4000 {
DiagSeverity::Fatal
} else if raw < 5000 {
DiagSeverity::Remark
} else {
DiagSeverity::Ignored
}
}
pub fn default_message(&self) -> &'static str {
match self {
Self::WUnusedVariable => "unused variable %0",
Self::WUnusedParameter => "unused parameter %0",
Self::WUnusedFunction => "unused function %0",
Self::WUnusedLabel => "unused label %0",
Self::WUnusedValue => "unused value",
Self::WUnusedResult => "unused result",
Self::WUnusedLocalTypedef => "unused typedef %0",
Self::WUnusedPrivateField => "private field %0 is not used",
Self::WUnusedLambdaCapture => "lambda capture %0 is not used",
Self::WUnusedConstVariable => "unused variable %0",
Self::WUnusedButSetVariable => "variable %0 set but not used",
Self::WUnusedButSetParameter => "parameter %0 set but not used",
Self::WSignCompare => "comparison of integers of different signs: %0 and %1",
Self::WSignConversion => "implicit conversion changes signedness: %0 to %1",
Self::WSignPromo => "promotion of %0 to %1 changes signedness",
Self::WConversion => "implicit conversion from %0 to %1",
Self::WConversionLoss => "implicit conversion loses integer precision: %0 to %1",
Self::WShorten64To32 => "implicit conversion loses integer precision: %0 to %1",
Self::WBitFieldConversion => "implicit truncation from %0 to bit-field %1",
Self::WImplicitIntConversion => "implicit integer conversion changes value: %0 to %1",
Self::WFormat => "format string is not a string literal",
Self::WFormatSecurity => "format string is not a string literal (potentially insecure)",
Self::WFormatExtraArgs => "data argument not used by format string",
Self::WFormatInvalidSpecifier => "invalid conversion specifier '%0'",
Self::WShadow => "declaration shadows a %select{local|variable|field}0",
Self::WShadowField => "non-static data member %0 shadows member inherited from %1",
Self::WShadowIvar => "local declaration of %0 shadows instance variable",
Self::WShadowAll => "declaration shadows a %select{...|...|...}0",
Self::WParentheses => "operator '%0' has lower precedence than '%1'",
Self::WParenthesesEquality => "equality comparison with extraneous parentheses",
Self::WParenthesesBitwise => "'&' within '|'",
Self::WLogicalOpParentheses => "'&&' within '||'",
Self::WShiftOpParentheses => "operator '<<' has lower precedence than '%0'",
Self::WDanglingElse => "add explicit braces to avoid dangling else",
Self::WSwitch => "enumeration %select{value|values}0 not handled in switch",
Self::WSwitchEnum => "enumeration value %0 not handled in switch",
Self::WSwitchDefault => "default label in switch which covers all enumeration values",
Self::WImplicitFallthrough => "unannotated fall-through between switch labels",
Self::WStrictAliasing => "dereferencing type-punned pointer will break strict-aliasing rules",
Self::WStrictAliasing1 => "dereferencing type-punned pointer might break strict-aliasing rules",
Self::WStrictAliasing2 => "type-punning to incomplete type might break strict-aliasing rules",
Self::WReturnType => "control reaches end of non-void function",
Self::WReturnStackAddress => "returning address of local temporary object",
Self::WReturnLocalAddr => "returning address of local variable or temporary",
Self::WUninitialized => "variable %0 is uninitialized when used here",
Self::WSometimesUninitialized => "variable %0 is possibly uninitialized",
Self::WConditionalUninitialized => "variable %0 may be uninitialized when used here",
Self::WMaybeUninitialized => "variable %0 may be uninitialized when %select{...|...}1",
Self::WTautologicalCompare => "comparison of %0 with %1 is always %select{true|false}2",
Self::WTautologicalConstantCompare => "result of comparison of constant %0 with %1 is always %select{true|false}2",
Self::WTautologicalPointerCompare => "comparison of distinct pointer types (%0 and %1) always evaluates to %select{true|false}2",
Self::WNullDereference => "dereference of null pointer",
Self::WDivisionByZero => "division by zero is undefined",
Self::WArrayBounds => "array index %0 is past the end of the array",
Self::WUnreachableCode => "will never be executed",
Self::WUnreachableCodeBreak => "code will never be executed",
Self::WUnreachableCodeReturn => "code will never be executed",
Self::WCharSubscripts => "array subscript is of type 'char'",
Self::WMainReturnType => "return type of 'main' is not 'int'",
Self::WMissingBraces => "suggest braces around initialization of subobject",
Self::WMissingFieldInitializers => "missing field '%0' initializer",
Self::WDeprecated => "%0 is deprecated",
Self::WDeprecatedDeclarations => "%0 is deprecated",
Self::WDeprecatedCopy => "definition of implicit copy %select{constructor|assignment operator}0 is deprecated",
Self::WDeprecatedRegister => "'register' storage class specifier is deprecated",
Self::WDocumentation => "not a Doxygen trailing comment",
Self::WDocumentationUnknownCommand => "unknown command tag name '%0'",
Self::WDocumentationHTML => "HTML tag '%0' requires an end tag",
Self::WExtraSemi => "extra ';' after member function definition",
Self::WExtraSemiStmt => "empty expression statement has no effect; remove unnecessary ';'",
Self::WExtraQualification => "extra qualification on member '%0'",
Self::WOverloadedVirtual => "%0 hides overloaded virtual %select{function|functions}1",
Self::WHiddenVirtual => "hidden overloaded virtual function %0",
Self::WReorder => "field %0 will be initialized after %select{field|base}1 %2",
Self::WUnknownAttributes => "unknown attribute %0 ignored",
Self::WIgnoredAttributes => "%0 attribute ignored",
Self::WPointerArith => "arithmetic on a pointer to void",
Self::WPointerToIntCast => "cast to smaller integer type %0 from %1",
Self::WFloatEqual => "comparing floating point with == or != is unsafe",
Self::WUndef => "'%0' is not defined, evaluates to 0",
Self::WComment => "'/*' within block comment",
Self::WEmptyBody => "if statement has empty body",
Self::WNewlineEOF => "no newline at end of file",
Self::WLongLong => "'long long' is an extension",
Self::WOldStyleCast => "use of old-style cast",
Self::WVLA => "variable length array used",
Self::WWriteStrings => "conversion from string literal to 'char *' is deprecated",
Self::WZeroLengthArray => "zero size arrays are an extension",
Self::ErrExpectedSemiAfterExpr => "expected ';' after expression",
Self::ErrExpectedSemiAfterDecl => "expected ';' after declaration",
Self::ErrExpectedExpression => "expected expression",
Self::ErrExpectedType => "expected a type",
Self::ErrExpectedIdentifier => "expected identifier",
Self::ErrExpectedRBrace => "expected '}'",
Self::ErrExpectedRParen => "expected ')'",
Self::ErrExpectedRBracket => "expected ']'",
Self::ErrExpectedComma => "expected ','",
Self::ErrExpectedColon => "expected ':'",
Self::ErrExpectedFuncBody => "expected function body after function declarator",
Self::ErrExpectedStatement => "expected statement",
Self::ErrExpectedDeclaration => "expected declaration",
Self::ErrExpectedParameter => "expected parameter declarator",
Self::ErrExpectedTemplateArg => "expected template argument",
Self::ErrExpectedConstantExpression => "expected constant expression",
Self::ErrExpectedStringLiteral => "expected string literal",
Self::ErrExpectedIntegerLiteral => "expected integer literal",
Self::ErrUndeclaredIdentifier => "use of undeclared identifier '%0'",
Self::ErrUndeclaredVarUse => "use of undeclared identifier '%0'",
Self::ErrUndeclaredFunction => "use of undeclared identifier '%0'; did you mean '%1'?",
Self::ErrRedefinition => "redefinition of '%0'",
Self::ErrRedefinitionDifferentType => "redefinition of '%0' with a different type: %1 vs %2",
Self::ErrConflictTypes => "conflicting types for '%0'",
Self::ErrCannotInitialize => "cannot initialize %select{variable|return object|parameter}0 of type %1 with an %select{lvalue|rvalue}2 of type %3",
Self::ErrCannotInitializeWithInitList => "cannot initialize %0 with an initializer list",
Self::ErrCannotConvert => "cannot convert '%0' to '%1'",
Self::ErrCannotConvertToPointer => "cannot convert '%0' to a pointer type '%1'",
Self::ErrIncompatibleTypes => "incompatible types %0 and %1",
Self::ErrIncompatiblePointerTypes => "incompatible pointer types passing %0 to parameter of type %1",
Self::ErrNoMatchingFunction => "no matching function for call to '%0'",
Self::ErrNoMatchingMemberFunction => "no matching member function for call to '%0'",
Self::ErrNoMatchingConstructor => "no matching constructor for initialization of '%0'",
Self::ErrTooManyArgs => "too many arguments to function call, expected %0, have %1",
Self::ErrTooFewArgs => "too few arguments to function call, expected %0, have %1",
Self::ErrCallToDeleted => "call to deleted %select{function|constructor|member function}0 '%1'",
Self::ErrCallToInaccessible => "%0 is a %select{private|protected}1 member of %2",
Self::ErrUnaryAmpersandOnTemporary => "taking the address of a temporary object of type '%0'",
Self::ErrUnaryAmpersandOnBitfield => "address of bit-field requested",
Self::ErrDivisionByZeroConst => "division by zero is undefined",
Self::ErrShiftByNegative => "shift count is negative",
Self::ErrShiftByExcess => "shift count >= width of type",
Self::ErrAssignmentToConst => "cannot assign to variable %0 with const-qualified type '%1'",
Self::ErrIncrementOfBoolean => "cannot increment value of type 'bool'",
Self::ErrAbstractClass => "allocating an object of abstract class type '%0'",
Self::ErrPrivateMember => "%0 is a private member of %1",
Self::ErrProtectedMember => "%0 is a protected member of %1",
Self::ErrInvalidUseOfThis => "invalid use of 'this' outside of a non-static member function",
Self::ErrInvalidUseOfIncompleteType => "invalid use of incomplete type '%0'",
Self::ErrSizeOfIncompleteType => "invalid application of 'sizeof' to an incomplete type '%0'",
Self::ErrDeleteOfIncompleteType => "deleting pointer to incomplete type '%0' may cause undefined behavior",
Self::ErrPPHashError => "%0",
Self::ErrPPIncludeNotFound => "'%0' file not found",
Self::ErrPPIncludeTooDeep => "#include nested too deeply",
Self::ErrPPMacroRedefined => "macro '%0' redefined",
Self::ErrPPMacroNotDefined => "macro '%0' is not defined",
Self::ErrPPInvalidDirective => "invalid preprocessing directive: %0",
Self::ErrPPBadPaste => "pasting formed '%0', an invalid preprocessing token",
Self::ErrPPUnterminatedComment => "unterminated /* comment",
Self::ErrPPMissingEndif => "missing #endif",
Self::ErrPPExtraEndif => "unexpected #endif",
Self::ErrStaticAssert => "static assertion failed",
Self::ErrStaticAssertFailed => "static assertion failed: %0",
Self::ErrInvalidArraySize => "array size is not an integer constant",
Self::ErrNegativeArraySize => "array size is negative",
Self::ErrZeroSizeArray => "zero-length arrays are not permitted in C++",
Self::ErrFlexibleArrayMember => "flexible array member %0 in an otherwise empty %select{struct|class}1",
Self::ErrDuplicateMember => "duplicate member '%0'",
Self::NotePreviousDefinition => "previous definition is here",
Self::NotePreviousDeclaration => "previous declaration is here",
Self::NotePreviousImplicitDeclaration => "previous implicit declaration is here",
Self::NoteCandidateFunction => "candidate function not viable: %0",
Self::NoteCandidateConstructor => "candidate constructor not viable: %0",
Self::NoteCandidateTemplate => "candidate template ignored: %0",
Self::NoteInInstantiationOf => "in instantiation of %0 requested here",
Self::NoteInExpansionOf => "in expansion of macro '%0'",
Self::NoteForwardDeclaration => "forward declaration of %0",
Self::NoteDeclarationHere => "declared here",
Self::NoteDefinitionHere => "defined here",
Self::NoteMacroDefinedHere => "macro '%0' defined here",
Self::NoteMacroExpandedHere => "expanded from macro '%0'",
Self::NoteUninitializedHere => "variable %0 is declared here",
Self::NoteAssignedHere => "assigned here",
Self::NoteConditionHere => "condition is here",
Self::NoteLoopHere => "loop is here",
Self::NoteTypeMismatch => "type mismatch: %0 vs %1",
Self::NoteParameterHere => "passing argument to parameter %0 here",
Self::NoteReturnValueHere => "returning value here",
Self::NoteDestructorHere => "destructor of %0 is implicitly deleted",
Self::NoteConstructorHere => "constructor of %0 is implicitly deleted",
Self::NoteBaseClassHere => "base class %0 specified here",
Self::NoteMemberDeclaredHere => "member %0 declared here",
Self::NoteShadowDeclHere => "previous declaration is here",
Self::FatalNoInputFiles => "no input files",
Self::FatalCannotOpenFile => "cannot open file '%0': %1",
Self::FatalCannotWriteFile => "cannot write file '%0': %1",
Self::FatalOutOfMemory => "out of memory",
Self::FatalTooManyErrors => "too many errors emitted, stopping now",
Self::FatalInvalidTarget => "invalid target '%0'",
Self::FatalUnsupportedTarget => "unsupported target '%0'",
Self::FatalInvalidOption => "invalid option '%0'",
Self::FatalConflictingOptions => "conflicting options '%0' and '%1'",
Self::FatalModuleBuildFailed => "module build failed: %0",
Self::FatalPCHOutOfDate => "PCH file '%0' is out of date",
Self::RemarkLoopVectorized => "vectorized loop",
Self::RemarkLoopInterleaved => "interleaved loop (interleaved count: %0)",
Self::RemarkSLPVectorized => "SLP vectorized",
Self::RemarkInlinedFunction => "'%0' inlined into '%1'",
Self::RemarkNotInlined => "'%0' not inlined into '%1' because %2",
Self::RemarkLoopUnrolled => "unrolled loop",
Self::RemarkLoopFused => "fused loop",
Self::RemarkLoopDistributed => "distributed loop",
Self::RemarkGVNLoadEliminated => "load eliminated by GVN",
Self::RemarkMemCpyOptimized => "memcpy optimized",
Self::RemarkInlineCost => "inline cost for '%0' is %1",
Self::RemarkCandidateDisabled => "candidate function disabled: %0",
Self::RemarkCandidateNotViable => "candidate function not viable: %0",
Self::RemarkTemplateDeductionFailure => "template argument deduction failure: %0",
Self::RemarkOverloadCandidateDropped => "overload candidate dropped: %0",
Self::IgnoredMacroDefined => "macro '%0' defined",
Self::IgnoredMacroUndefined => "macro '%0' undefined",
Self::IgnoredPragma => "pragma %0 ignored",
Self::IgnoredAttribute => "attribute %0 ignored",
Self::IgnoredExtension => "extension %0 used",
_ => "diagnostic %0",
}
}
pub fn flag_name(&self) -> Option<&'static str> {
match self {
Self::WUnusedVariable => Some("-Wunused-variable"),
Self::WUnusedParameter => Some("-Wunused-parameter"),
Self::WUnusedFunction => Some("-Wunused-function"),
Self::WUnusedLabel => Some("-Wunused-label"),
Self::WUnusedValue => Some("-Wunused-value"),
Self::WUnusedResult => Some("-Wunused-result"),
Self::WUnusedLocalTypedef => Some("-Wunused-local-typedef"),
Self::WUnusedPrivateField => Some("-Wunused-private-field"),
Self::WUnusedConstVariable => Some("-Wunused-const-variable"),
Self::WUnusedButSetVariable => Some("-Wunused-but-set-variable"),
Self::WUnusedButSetParameter => Some("-Wunused-but-set-parameter"),
Self::WUnusedMacro => Some("-Wunused-macros"),
Self::WUnusedMemberFunction => Some("-Wunused-member-function"),
Self::WUnusedTemplate => Some("-Wunused-template"),
Self::WSignCompare => Some("-Wsign-compare"),
Self::WSignConversion => Some("-Wsign-conversion"),
Self::WSignPromo => Some("-Wsign-promo"),
Self::WConversion => Some("-Wconversion"),
Self::WConversionLoss => Some("-Wconversion"),
Self::WShorten64To32 => Some("-Wshorten-64-to-32"),
Self::WImplicitIntConversion => Some("-Wimplicit-int-conversion"),
Self::WImplicitFloatConversion => Some("-Wimplicit-float-conversion"),
Self::WEnumConversion => Some("-Wenum-conversion"),
Self::WNullableToNonNullable => Some("-Wnullable-to-nonnull-conversion"),
Self::WFormat => Some("-Wformat"),
Self::WFormatSecurity => Some("-Wformat-security"),
Self::WFormatExtraArgs => Some("-Wformat-extra-args"),
Self::WFormatInvalidSpecifier => Some("-Wformat-invalid-specifier"),
Self::WShadow => Some("-Wshadow"),
Self::WShadowField => Some("-Wshadow-field"),
Self::WShadowIvar => Some("-Wshadow-ivar"),
Self::WShadowAll => Some("-Wshadow-all"),
Self::WShadowUncapturedLocal => Some("-Wshadow-uncaptured-local"),
Self::WParentheses => Some("-Wparentheses"),
Self::WParenthesesEquality => Some("-Wparentheses-equality"),
Self::WLogicalOpParentheses => Some("-Wlogical-op-parentheses"),
Self::WShiftOpParentheses => Some("-Wshift-op-parentheses"),
Self::WDanglingElse => Some("-Wdangling-else"),
Self::WSwitch => Some("-Wswitch"),
Self::WSwitchEnum => Some("-Wswitch-enum"),
Self::WSwitchDefault => Some("-Wswitch-default"),
Self::WSwitchBool => Some("-Wswitch-bool"),
Self::WSwitchOutOfRange => Some("-Wswitch-outside-range"),
Self::WSwitchUnreachableDefault => Some("-Wswitch-unreachable-default"),
Self::WCoveredSwitchDefault => Some("-Wcovered-switch-default"),
Self::WImplicitFallthrough => Some("-Wimplicit-fallthrough"),
Self::WStrictAliasing => Some("-Wstrict-aliasing"),
Self::WStrictAliasing1 => Some("-Wstrict-aliasing=1"),
Self::WStrictAliasing2 => Some("-Wstrict-aliasing=2"),
Self::WReturnType => Some("-Wreturn-type"),
Self::WReturnStackAddress => Some("-Wreturn-stack-address"),
Self::WReturnLocalAddr => Some("-Wreturn-local-addr"),
Self::WUninitialized => Some("-Wuninitialized"),
Self::WSometimesUninitialized => Some("-Wsometimes-uninitialized"),
Self::WConditionalUninitialized => Some("-Wconditional-uninitialized"),
Self::WMaybeUninitialized => Some("-Wmaybe-uninitialized"),
Self::WTautologicalCompare => Some("-Wtautological-compare"),
Self::WTautologicalConstantCompare => Some("-Wtautological-constant-compare"),
Self::WTautologicalPointerCompare => Some("-Wtautological-pointer-compare"),
Self::WTautologicalOverlapCompare => Some("-Wtautological-overlap-compare"),
Self::WTautologicalTypeLimitCompare => Some("-Wtautological-type-limit-compare"),
Self::WTautologicalOutOfRange => Some("-Wtautological-out-of-range"),
Self::WNullDereference => Some("-Wnull-dereference"),
Self::WDivisionByZero => Some("-Wdivision-by-zero"),
Self::WArrayBounds => Some("-Warray-bounds"),
Self::WUnreachableCode => Some("-Wunreachable-code"),
Self::WCharSubscripts => Some("-Wchar-subscripts"),
Self::WMain => Some("-Wmain"),
Self::WMissingBraces => Some("-Wmissing-braces"),
Self::WMissingFieldInitializers => Some("-Wmissing-field-initializers"),
Self::WMissingPrototypes => Some("-Wmissing-prototypes"),
Self::WMissingVariableDeclarations => Some("-Wmissing-variable-declarations"),
Self::WDeprecated => Some("-Wdeprecated"),
Self::WDeprecatedDeclarations => Some("-Wdeprecated-declarations"),
Self::WDeprecatedCopy => Some("-Wdeprecated-copy"),
Self::WDeprecatedRegister => Some("-Wdeprecated-register"),
Self::WDocumentation => Some("-Wdocumentation"),
Self::WDocumentationUnknownCommand => Some("-Wdocumentation-unknown-command"),
Self::WExtraSemi => Some("-Wextra-semi"),
Self::WExtraSemiStmt => Some("-Wextra-semi-stmt"),
Self::WExtraQualification => Some("-Wextra-qualification"),
Self::WOverloadedVirtual => Some("-Woverloaded-virtual"),
Self::WHiddenVirtual => Some("-Whidden-virtual"),
Self::WReorder => Some("-Wreorder"),
Self::WSuggestOverride => Some("-Wsuggest-override"),
Self::WInconsistentMissingOverride => Some("-Winconsistent-missing-override"),
Self::WUnknownAttributes => Some("-Wunknown-attributes"),
Self::WIgnoredAttributes => Some("-Wignored-attributes"),
Self::WAll => Some("-Wall"),
Self::WExtra => Some("-Wextra"),
Self::WPedantic => Some("-Wpedantic"),
Self::WPointerArith => Some("-Wpointer-arith"),
Self::WPointerToIntCast => Some("-Wpointer-to-int-cast"),
Self::WIntToPointerCast => Some("-Wint-to-pointer-cast"),
Self::WCastAlign => Some("-Wcast-align"),
Self::WCastQual => Some("-Wcast-qual"),
Self::WFloatEqual => Some("-Wfloat-equal"),
Self::WUndef => Some("-Wundef"),
Self::WComment => Some("-Wcomment"),
Self::WMultichar => Some("-Wmultichar"),
Self::WEmptyBody => Some("-Wempty-body"),
Self::WNewlineEOF => Some("-Wnewline-eof"),
Self::WLongLong => Some("-Wlong-long"),
Self::WOldStyleCast => Some("-Wold-style-cast"),
Self::WVLA => Some("-Wvla"),
Self::WWriteStrings => Some("-Wwrite-strings"),
Self::WZeroLengthArray => Some("-Wzero-length-array"),
Self::WNonVirtualDtor => Some("-Wnon-virtual-dtor"),
Self::WThreadSafety => Some("-Wthread-safety"),
Self::WStringConversion => Some("-Wstring-conversion"),
Self::WVarargs => Some("-Wvarargs"),
_ => None,
}
}
pub fn groups(&self) -> &[&'static str] {
match self {
Self::WUnusedVariable
| Self::WUnusedParameter
| Self::WUnusedFunction
| Self::WUnusedLabel
| Self::WUnusedValue
| Self::WUnusedResult
| Self::WUnusedLocalTypedef
| Self::WUnusedPrivateField
| Self::WUnusedLambdaCapture
| Self::WUnusedConstVariable
| Self::WUnusedButSetVariable
| Self::WUnusedButSetParameter
| Self::WUnusedMacro
| Self::WUnusedMemberFunction
| Self::WUnusedTemplate => &["-Wunused"],
Self::WSignCompare | Self::WSignConversion => &["-Wsign-compare", "-Wextra"],
Self::WConversion
| Self::WConversionLoss
| Self::WShorten64To32
| Self::WImplicitIntConversion => &["-Wconversion"],
Self::WEnumConversion => &["-Wenum-conversion", "-Wconversion"],
Self::WFormat
| Self::WFormatSecurity
| Self::WFormatExtraArgs
| Self::WFormatInvalidSpecifier => &["-Wformat"],
Self::WShadow | Self::WShadowField | Self::WShadowIvar => &["-Wshadow"],
Self::WParentheses | Self::WParenthesesEquality | Self::WLogicalOpParentheses => {
&["-Wparentheses", "-Wall"]
}
Self::WShiftOpParentheses => &["-Wshift-op-parentheses", "-Wparentheses"],
Self::WDanglingElse => &["-Wdangling-else", "-Wparentheses"],
Self::WSwitch | Self::WSwitchEnum | Self::WSwitchDefault => &["-Wswitch"],
Self::WImplicitFallthrough => &["-Wimplicit-fallthrough"],
Self::WStrictAliasing | Self::WStrictAliasing1 | Self::WStrictAliasing2 => {
&["-Wstrict-aliasing"]
}
Self::WReturnType => &["-Wreturn-type"],
Self::WReturnStackAddress | Self::WReturnLocalAddr => &["-Wreturn-local-addr"],
Self::WUninitialized
| Self::WSometimesUninitialized
| Self::WConditionalUninitialized => &["-Wuninitialized"],
Self::WMaybeUninitialized => &["-Wconditional-uninitialized", "-Wuninitialized"],
Self::WTautologicalCompare | Self::WTautologicalConstantCompare => {
&["-Wtautological-compare"]
}
Self::WNullDereference => &["-Wnull-dereference"],
Self::WDivisionByZero => &["-Wdivision-by-zero"],
Self::WArrayBounds => &["-Warray-bounds"],
Self::WUnreachableCode => &["-Wunreachable-code"],
Self::WCharSubscripts => &["-Wchar-subscripts", "-Wall"],
Self::WMain => &["-Wmain"],
Self::WMissingBraces => &["-Wmissing-braces", "-Wall"],
Self::WMissingFieldInitializers => &["-Wmissing-field-initializers", "-Wextra"],
Self::WDeprecatedDeclarations | Self::WDeprecatedCopy | Self::WDeprecatedRegister => {
&["-Wdeprecated"]
}
Self::WDocumentation | Self::WDocumentationUnknownCommand => &["-Wdocumentation"],
Self::WExtraSemi | Self::WExtraSemiStmt => &["-Wextra-semi"],
Self::WExtraQualification => &["-Wextra-qualification"],
Self::WOverloadedVirtual | Self::WHiddenVirtual => &["-Woverloaded-virtual"],
Self::WReorder => &["-Wreorder", "-Wall"],
Self::WSuggestOverride => &["-Wsuggest-override"],
Self::WInconsistentMissingOverride => &["-Winconsistent-missing-override"],
Self::WUnknownAttributes | Self::WIgnoredAttributes => &["-Wattributes"],
Self::WExtra => &["-Wextra"],
Self::WAll => &["-Wall"],
Self::WPedantic => &["-Wpedantic"],
Self::WPointerArith => &["-Wpointer-arith", "-Wpedantic"],
Self::WPointerToIntCast | Self::WIntToPointerCast => &["-Wpointer-to-int-cast"],
Self::WCastAlign => &["-Wcast-align"],
Self::WCastQual => &["-Wcast-qual"],
Self::WFloatEqual => &["-Wfloat-equal"],
Self::WUndef => &["-Wundef"],
Self::WComment => &["-Wcomment", "-Wall"],
Self::WEmptyBody => &["-Wempty-body"],
Self::WNewlineEOF => &["-Wnewline-eof", "-Wpedantic"],
Self::WLongLong => &["-Wlong-long", "-Wpedantic"],
Self::WOldStyleCast => &["-Wold-style-cast"],
Self::WVLA => &["-Wvla", "-Wpedantic"],
Self::WWriteStrings => &["-Wwrite-strings"],
Self::WZeroLengthArray => &["-Wzero-length-array", "-Wpedantic"],
Self::WNonVirtualDtor => &["-Wnon-virtual-dtor", "-Weffc++"],
Self::WThreadSafety => &["-Wthread-safety"],
Self::WVarargs => &["-Wvarargs"],
_ => &[],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DiagSeverity {
Ignored = 0,
Remark = 1,
Note = 2,
Warning = 3,
Error = 4,
Fatal = 5,
}
impl DiagSeverity {
pub fn as_str(&self) -> &'static str {
match self {
Self::Ignored => "ignored",
Self::Remark => "remark",
Self::Note => "note",
Self::Warning => "warning",
Self::Error => "error",
Self::Fatal => "fatal error",
}
}
pub fn is_error(&self) -> bool {
matches!(self, Self::Error | Self::Fatal)
}
pub fn is_warning(&self) -> bool {
*self == Self::Warning
}
pub fn is_fatal(&self) -> bool {
*self == Self::Fatal
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"ignored" | "off" => Some(Self::Ignored),
"remark" => Some(Self::Remark),
"note" => Some(Self::Note),
"warning" | "warn" => Some(Self::Warning),
"error" | "err" => Some(Self::Error),
"fatal" | "fatal error" => Some(Self::Fatal),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiagMapping {
pub name: String,
pub severity: DiagSeverity,
pub is_user_override: bool,
}
impl DiagMapping {
pub fn new(name: &str, severity: DiagSeverity) -> Self {
Self {
name: name.to_string(),
severity,
is_user_override: false,
}
}
pub fn user_override(name: &str, severity: DiagSeverity) -> Self {
Self {
name: name.to_string(),
severity,
is_user_override: true,
}
}
}
#[derive(Debug, Clone)]
pub struct DiagState {
pub overrides: HashMap<DiagID, DiagSeverity>,
pub groups: HashMap<String, DiagSeverity>,
pub flag_to_id: HashMap<String, DiagID>,
}
impl DiagState {
pub fn new() -> Self {
let mut state = Self {
overrides: HashMap::new(),
groups: HashMap::new(),
flag_to_id: HashMap::new(),
};
state.register_all_diagnostics();
state.set_group("Wall", DiagSeverity::Warning);
state
}
fn register_all_diagnostics(&mut self) {
use DiagID::*;
let all_diags: &[DiagID] = &[
WUnusedVariable,
WUnusedParameter,
WUnusedFunction,
WUnusedLabel,
WUnusedValue,
WUnusedResult,
WUnusedLocalTypedef,
WUnusedPrivateField,
WUnusedConstVariable,
WUnusedButSetVariable,
WUnusedButSetParameter,
WSignCompare,
WSignConversion,
WConversion,
WConversionLoss,
WShorten64To32,
WImplicitIntConversion,
WEnumConversion,
WFormat,
WFormatSecurity,
WFormatExtraArgs,
WFormatInvalidSpecifier,
WShadow,
WShadowField,
WShadowIvar,
WParentheses,
WParenthesesEquality,
WLogicalOpParentheses,
WShiftOpParentheses,
WDanglingElse,
WSwitch,
WSwitchEnum,
WSwitchDefault,
WImplicitFallthrough,
WStrictAliasing,
WStrictAliasing1,
WStrictAliasing2,
WReturnType,
WReturnStackAddress,
WReturnLocalAddr,
WUninitialized,
WSometimesUninitialized,
WConditionalUninitialized,
WMaybeUninitialized,
WTautologicalCompare,
WTautologicalConstantCompare,
WTautologicalPointerCompare,
WNullDereference,
WDivisionByZero,
WArrayBounds,
WUnreachableCode,
WCharSubscripts,
WMain,
WMissingBraces,
WMissingFieldInitializers,
WDeprecatedDeclarations,
WDeprecatedCopy,
WDeprecatedRegister,
WDocumentation,
WDocumentationUnknownCommand,
WExtraSemi,
WExtraSemiStmt,
WExtraQualification,
WOverloadedVirtual,
WHiddenVirtual,
WReorder,
WSuggestOverride,
WInconsistentMissingOverride,
WUnknownAttributes,
WIgnoredAttributes,
WAll,
WExtra,
WPedantic,
WPointerArith,
WPointerToIntCast,
WIntToPointerCast,
WCastAlign,
WCastQual,
WFloatEqual,
WUndef,
WComment,
WEmptyBody,
WNewlineEOF,
WLongLong,
WOldStyleCast,
WVLA,
WWriteStrings,
WZeroLengthArray,
WNonVirtualDtor,
WThreadSafety,
WVarargs,
ErrExpectedSemiAfterExpr,
ErrExpectedSemiAfterDecl,
ErrExpectedExpression,
ErrUndeclaredIdentifier,
ErrRedefinition,
ErrRedefinitionDifferentType,
ErrCannotInitialize,
ErrCannotInitializeWithInitList,
ErrCannotConvert,
ErrNoMatchingFunction,
ErrNoMatchingMemberFunction,
ErrNoMatchingConstructor,
ErrTooManyArgs,
ErrTooFewArgs,
ErrCallToDeleted,
ErrCallToInaccessible,
ErrUnaryAmpersandOnTemporary,
ErrUnaryAmpersandOnBitfield,
ErrDivisionByZeroConst,
ErrShiftByNegative,
ErrShiftByExcess,
ErrAbstractClass,
ErrPrivateMember,
ErrProtectedMember,
ErrInvalidUseOfThis,
ErrInvalidUseOfIncompleteType,
ErrSizeOfIncompleteType,
ErrDeleteOfIncompleteType,
ErrPPHashError,
ErrPPIncludeNotFound,
ErrPPIncludeTooDeep,
ErrPPMacroRedefined,
ErrPPMacroNotDefined,
ErrPPInvalidDirective,
ErrPPBadPaste,
ErrPPUnterminatedComment,
ErrPPMissingEndif,
ErrPPExtraEndif,
ErrStaticAssert,
ErrStaticAssertFailed,
ErrInvalidArraySize,
ErrNegativeArraySize,
ErrZeroSizeArray,
ErrFlexibleArrayMember,
ErrDuplicateMember,
NotePreviousDefinition,
NotePreviousDeclaration,
NoteCandidateFunction,
NoteInInstantiationOf,
NoteInExpansionOf,
NoteForwardDeclaration,
NoteDeclarationHere,
NoteDefinitionHere,
NoteMacroDefinedHere,
NoteUninitializedHere,
NoteAssignedHere,
NoteParameterHere,
NoteReturnValueHere,
NoteBaseClassHere,
NoteMemberDeclaredHere,
FatalNoInputFiles,
FatalCannotOpenFile,
FatalCannotWriteFile,
FatalOutOfMemory,
FatalTooManyErrors,
FatalInvalidTarget,
FatalUnsupportedTarget,
FatalInvalidOption,
FatalConflictingOptions,
RemarkLoopVectorized,
RemarkLoopInterleaved,
RemarkSLPVectorized,
RemarkInlinedFunction,
RemarkNotInlined,
RemarkLoopUnrolled,
RemarkGVNLoadEliminated,
RemarkCandidateDisabled,
RemarkCandidateNotViable,
RemarkTemplateDeductionFailure,
IgnoredMacroDefined,
IgnoredPragma,
IgnoredAttribute,
IgnoredExtension,
];
for diag in all_diags {
if let Some(flag) = diag.flag_name() {
self.flag_to_id.insert(flag.to_string(), *diag);
}
}
}
pub fn set_severity(&mut self, id: DiagID, severity: DiagSeverity) {
self.overrides.insert(id, severity);
}
pub fn set_group(&mut self, group: &str, severity: DiagSeverity) {
self.groups.insert(group.to_string(), severity);
}
pub fn get_severity(&self, id: DiagID) -> DiagSeverity {
if let Some(sev) = self.overrides.get(&id) {
return *sev;
}
let groups = id.groups();
let mut max_sev = id.default_severity();
for group in groups {
if let Some(sev) = self.groups.get(*group) {
if *sev > max_sev {
max_sev = *sev;
}
}
}
if matches!(
id,
DiagID::WParentheses
| DiagID::WCharSubscripts
| DiagID::WMissingBraces
| DiagID::WComment
| DiagID::WReorder
) {
if let Some(_sev) = self.groups.get("Wall") {
if max_sev < DiagSeverity::Warning {
max_sev = DiagSeverity::Warning;
}
}
}
max_sev
}
pub fn is_enabled(&self, id: DiagID) -> bool {
self.get_severity(id) != DiagSeverity::Ignored
}
pub fn set_mapping(&mut self, flag: &str, severity: DiagSeverity) {
if let Some(id) = self.flag_to_id.get(flag).copied() {
self.set_severity(id, severity);
} else {
self.set_group(flag, severity);
}
}
pub fn reset(&mut self) {
self.overrides.clear();
self.groups.clear();
}
}
impl Default for DiagState {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticOutputFormat {
Clang,
MSVC,
GCC,
Vi,
JSON,
SARIF,
Serialized,
}
pub trait DiagnosticConsumer {
fn begin_diagnostic(&mut self, diag: &StructuredDiagnostic);
fn end_diagnostic(&mut self);
fn handle_note(&mut self, note: &DiagnosticNote);
fn handle_fixit(&mut self, fixit: &FixItHint);
fn handle_range(&mut self, range: &SourceRange);
fn flush(&mut self);
fn error_count(&self) -> usize;
fn warning_count(&self) -> usize;
fn clear(&mut self);
}
#[derive(Debug, Clone)]
pub struct StructuredDiagnostic {
pub id: DiagID,
pub severity: DiagSeverity,
pub message: String,
pub location: ClangSourceLocation,
pub ranges: Vec<SourceRange>,
pub notes: Vec<DiagnosticNote>,
pub fixits: Vec<FixItHint>,
pub flag_name: Option<String>,
pub category: String,
}
impl StructuredDiagnostic {
pub fn new(
id: DiagID,
severity: DiagSeverity,
message: String,
location: ClangSourceLocation,
) -> Self {
Self {
id,
severity,
message,
location,
ranges: Vec::new(),
notes: Vec::new(),
fixits: Vec::new(),
flag_name: id.flag_name().map(|s| s.to_string()),
category: id.category().to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticNote {
pub message: String,
pub location: ClangSourceLocation,
pub ranges: Vec<SourceRange>,
}
impl DiagnosticNote {
pub fn new(message: &str, location: ClangSourceLocation) -> Self {
Self {
message: message.to_string(),
location,
ranges: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClangSourceLocation {
pub file: PathBuf,
pub line: u32,
pub column: u32,
pub offset: u64,
pub is_valid: bool,
pub is_macro_expansion: bool,
pub spelling_loc: Option<Box<ClangSourceLocation>>,
pub expansion_loc: Option<Box<ClangSourceLocation>>,
}
impl ClangSourceLocation {
pub fn new(file: &Path, line: u32, column: u32) -> Self {
Self {
file: file.to_path_buf(),
line,
column,
offset: 0,
is_valid: true,
is_macro_expansion: false,
spelling_loc: None,
expansion_loc: None,
}
}
pub fn invalid() -> Self {
Self {
file: PathBuf::new(),
line: 0,
column: 0,
offset: 0,
is_valid: false,
is_macro_expansion: false,
spelling_loc: None,
expansion_loc: None,
}
}
pub fn print(&self, f: &mut dyn fmt::Write) -> fmt::Result {
if self.is_valid {
write!(f, "{}:{}:{}", self.file.display(), self.line, self.column)
} else {
write!(f, "<invalid loc>")
}
}
pub fn filename(&self) -> &str {
self.file
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("<unknown>")
}
pub fn get_presumed_loc(&self) -> &Self {
self
}
pub fn get_spelling_loc(&self) -> &Self {
if let Some(ref spelling) = self.spelling_loc {
spelling
} else {
self
}
}
pub fn get_expansion_loc(&self) -> Option<&Self> {
self.expansion_loc.as_deref()
}
}
impl fmt::Display for ClangSourceLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.print(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceRange {
pub begin: ClangSourceLocation,
pub end: ClangSourceLocation,
}
impl SourceRange {
pub fn new(begin: ClangSourceLocation, end: ClangSourceLocation) -> Self {
Self { begin, end }
}
pub fn point(loc: ClangSourceLocation) -> Self {
Self {
begin: loc.clone(),
end: loc,
}
}
pub fn is_valid(&self) -> bool {
self.begin.is_valid && self.end.is_valid
}
pub fn is_single_line(&self) -> bool {
self.begin.line == self.end.line && self.begin.file == self.end.file
}
pub fn is_multiline(&self) -> bool {
!self.is_single_line()
}
}
#[derive(Debug, Clone)]
pub struct FixItHint {
pub remove_range: SourceRange,
pub code: String,
pub is_insertion: bool,
}
impl FixItHint {
pub fn insertion(loc: ClangSourceLocation, code: &str) -> Self {
Self {
remove_range: SourceRange::point(loc),
code: code.to_string(),
is_insertion: true,
}
}
pub fn removal(range: SourceRange) -> Self {
Self {
remove_range: range,
code: String::new(),
is_insertion: false,
}
}
pub fn replacement(range: SourceRange, code: &str) -> Self {
Self {
remove_range: range,
code: code.to_string(),
is_insertion: false,
}
}
}
#[derive(Debug, Clone)]
pub struct FullSourceLoc {
pub loc: ClangSourceLocation,
pub manager: ClangSourceManager,
}
impl FullSourceLoc {
pub fn new(loc: ClangSourceLocation, manager: ClangSourceManager) -> Self {
Self { loc, manager }
}
pub fn get_line_number(&self) -> u32 {
self.loc.line
}
pub fn get_column_number(&self) -> u32 {
self.loc.column
}
pub fn get_filename(&self) -> &str {
self.loc.filename()
}
pub fn is_valid(&self) -> bool {
self.loc.is_valid
}
pub fn get_source_line(&self) -> Option<String> {
self.manager.get_line(&self.loc.file, self.loc.line)
}
}
#[derive(Debug, Clone)]
pub struct ClangSourceManager {
pub files: HashMap<PathBuf, ClangSourceFile>,
}
#[derive(Debug, Clone)]
pub struct ClangSourceFile {
pub path: PathBuf,
pub content: String,
pub line_offsets: Vec<usize>,
}
impl ClangSourceFile {
pub fn new(path: &Path, content: &str) -> Self {
let mut line_offsets = vec![0];
for (i, c) in content.char_indices() {
if c == '\n' {
line_offsets.push(i + 1);
}
}
Self {
path: path.to_path_buf(),
content: content.to_string(),
line_offsets,
}
}
pub fn get_line(&self, line: u32) -> Option<String> {
if line < 1 || line as usize > self.line_offsets.len() {
return None;
}
let start = self.line_offsets[(line - 1) as usize];
let end = if (line as usize) < self.line_offsets.len() {
self.line_offsets[line as usize]
} else {
self.content.len()
};
let raw = &self.content[start..end];
let trimmed = raw.trim_end_matches('\n');
Some(trimmed.to_string())
}
pub fn get_range_text(
&self,
start_line: u32,
start_col: u32,
end_line: u32,
end_col: u32,
) -> Option<String> {
if start_line < 1 || end_line < 1 {
return None;
}
if start_line as usize > self.line_offsets.len()
|| end_line as usize > self.line_offsets.len()
{
return None;
}
let start_offset = self.line_offsets[(start_line - 1) as usize] + (start_col - 1) as usize;
let end_offset = if (end_line as usize) < self.line_offsets.len() {
self.line_offsets[end_line as usize] + (end_col - 1) as usize
} else {
self.content.len()
};
if start_offset <= end_offset && end_offset <= self.content.len() {
Some(self.content[start_offset..end_offset].to_string())
} else {
None
}
}
}
impl ClangSourceManager {
pub fn new() -> Self {
Self {
files: HashMap::new(),
}
}
pub fn add_file(&mut self, path: &Path, content: &str) {
let file = ClangSourceFile::new(path, content);
self.files.insert(path.to_path_buf(), file);
}
pub fn get_file(&self, path: &Path) -> Option<&ClangSourceFile> {
self.files.get(path)
}
pub fn get_line(&self, path: &Path, line: u32) -> Option<String> {
self.files.get(path).and_then(|f| f.get_line(line))
}
pub fn has_file(&self, path: &Path) -> bool {
self.files.contains_key(path)
}
pub fn file_count(&self) -> usize {
self.files.len()
}
}
impl Default for ClangSourceManager {
fn default() -> Self {
Self::new()
}
}
pub struct DiagnosticEngine {
pub state: DiagState,
pub consumers: Vec<Box<dyn DiagnosticConsumer>>,
pub source_manager: ClangSourceManager,
pub error_count: usize,
pub warning_count: usize,
pub error_limit: usize,
pub warnings_as_errors: bool,
pub ignore_warnings: bool,
pub emit_remarks: bool,
pub suppress_all: bool,
pub suppress_notes: bool,
pub show_fixits: bool,
pub output_format: DiagnosticOutputFormat,
pub color_mode: ColorMode,
pub tab_stop: u32,
pub message_length: u32,
#[allow(clippy::type_complexity)]
pub fatal_handler: Option<Box<dyn Fn(&StructuredDiagnostic) + Send + Sync>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorMode {
Auto,
Always,
Never,
}
impl DiagnosticEngine {
pub fn new(source_manager: ClangSourceManager) -> Self {
Self {
state: DiagState::new(),
consumers: Vec::new(),
source_manager,
error_count: 0,
warning_count: 0,
error_limit: 20,
warnings_as_errors: false,
ignore_warnings: false,
emit_remarks: false,
suppress_all: false,
suppress_notes: false,
show_fixits: true,
output_format: DiagnosticOutputFormat::Clang,
color_mode: ColorMode::Auto,
tab_stop: 8,
message_length: 0, fatal_handler: None,
}
}
pub fn add_consumer(&mut self, consumer: Box<dyn DiagnosticConsumer>) {
self.consumers.push(consumer);
}
pub fn set_severity(&mut self, id: DiagID, severity: DiagSeverity) {
self.state.set_severity(id, severity);
}
pub fn set_group_severity(&mut self, group: &str, severity: DiagSeverity) {
self.state.set_group(group, severity);
}
pub fn get_diagnostic_level(&self, id: DiagID) -> DiagSeverity {
self.state.get_severity(id)
}
pub fn set_diagnostic_mapping(&mut self, flag: &str, severity: DiagSeverity) {
self.state.set_mapping(flag, severity);
}
pub fn enable_all_warnings(&mut self) {
self.state.set_group("Wall", DiagSeverity::Warning);
self.state.set_group("Wextra", DiagSeverity::Warning);
}
pub fn enable_pedantic_warnings(&mut self) {
self.state.set_group("Wpedantic", DiagSeverity::Warning);
}
pub fn set_warnings_as_errors(&mut self, enable: bool) {
self.warnings_as_errors = enable;
}
pub fn set_ignore_warnings(&mut self, enable: bool) {
self.ignore_warnings = enable;
}
pub fn set_suppress_all(&mut self, enable: bool) {
self.suppress_all = enable;
}
pub fn emit(&mut self, id: DiagID, location: ClangSourceLocation, args: &[String]) {
if self.suppress_all {
return;
}
let mut severity = self.state.get_severity(id);
if self.ignore_warnings && severity == DiagSeverity::Warning {
return;
}
if self.warnings_as_errors && severity == DiagSeverity::Warning {
severity = DiagSeverity::Error;
}
if severity == DiagSeverity::Ignored {
return;
}
if severity == DiagSeverity::Remark && !self.emit_remarks {
return;
}
match severity {
DiagSeverity::Error | DiagSeverity::Fatal => {
self.error_count += 1;
if self.error_count >= self.error_limit && severity == DiagSeverity::Error {
severity = DiagSeverity::Fatal;
}
}
DiagSeverity::Warning => {
self.warning_count += 1;
}
_ => {}
}
let mut message = id.default_message().to_string();
for (i, arg) in args.iter().enumerate() {
let placeholder = format!("%{}", i);
message = message.replace(&placeholder, arg);
}
let diag = StructuredDiagnostic::new(id, severity, message, location);
for consumer in &mut self.consumers {
consumer.begin_diagnostic(&diag);
consumer.end_diagnostic();
}
if severity == DiagSeverity::Fatal {
if let Some(ref handler) = self.fatal_handler {
handler(&diag);
}
}
}
pub fn report(&mut self, id: DiagID) -> DiagnosticBuilder {
DiagnosticBuilder::new(self, id)
}
pub fn has_errors(&self) -> bool {
self.error_count > 0
}
pub fn has_warnings(&self) -> bool {
self.warning_count > 0
}
pub fn reset(&mut self) {
self.error_count = 0;
self.warning_count = 0;
for consumer in &mut self.consumers {
consumer.clear();
}
}
pub fn set_error_limit(&mut self, limit: usize) {
self.error_limit = limit;
}
pub fn set_output_format(&mut self, format: DiagnosticOutputFormat) {
self.output_format = format;
}
}
impl fmt::Debug for DiagnosticEngine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DiagnosticEngine")
.field("state", &self.state)
.field("consumers_count", &self.consumers.len())
.field("source_manager", &self.source_manager)
.field("error_count", &self.error_count)
.field("warning_count", &self.warning_count)
.field("error_limit", &self.error_limit)
.field("warnings_as_errors", &self.warnings_as_errors)
.field("ignore_warnings", &self.ignore_warnings)
.finish()
}
}
impl Default for DiagnosticEngine {
fn default() -> Self {
Self::new(ClangSourceManager::default())
}
}
#[derive(Debug)]
pub struct DiagnosticBuilder<'a> {
engine: &'a mut DiagnosticEngine,
id: DiagID,
severity: DiagSeverity,
message: String,
location: ClangSourceLocation,
ranges: Vec<SourceRange>,
notes: Vec<DiagnosticNote>,
fixits: Vec<FixItHint>,
args: Vec<String>,
}
impl<'a> DiagnosticBuilder<'a> {
fn new(engine: &'a mut DiagnosticEngine, id: DiagID) -> Self {
Self {
engine,
id,
severity: id.default_severity(),
message: String::new(),
location: ClangSourceLocation::invalid(),
ranges: Vec::new(),
notes: Vec::new(),
fixits: Vec::new(),
args: Vec::new(),
}
}
pub fn message(&mut self, msg: &str) -> &mut Self {
self.message = msg.to_string();
self
}
pub fn add_arg(&mut self, arg: &str) -> &mut Self {
self.args.push(arg.to_string());
self
}
pub fn append(&mut self, s: &str) -> &mut Self {
self.args.push(s.to_string());
self
}
pub fn append_range(&mut self, range: SourceRange) -> &mut Self {
self.ranges.push(range);
self
}
pub fn append_fixit(&mut self, fixit: FixItHint) -> &mut Self {
self.fixits.push(fixit);
self
}
pub fn at(&mut self, location: ClangSourceLocation) -> &mut Self {
self.location = location;
self
}
pub fn add_note(&mut self, message: &str, location: ClangSourceLocation) -> &mut Self {
self.notes.push(DiagnosticNote::new(message, location));
self
}
pub fn fixit_insertion(&mut self, loc: ClangSourceLocation, code: &str) -> &mut Self {
self.fixits.push(FixItHint::insertion(loc, code));
self
}
pub fn fixit_removal(&mut self, range: SourceRange) -> &mut Self {
self.fixits.push(FixItHint::removal(range));
self
}
pub fn fixit_replacement(&mut self, range: SourceRange, code: &str) -> &mut Self {
self.fixits.push(FixItHint::replacement(range, code));
self
}
pub fn severity(&mut self, sev: DiagSeverity) -> &mut Self {
self.severity = sev;
self
}
pub fn emit(&mut self) {
let final_message = if self.message.is_empty() {
let mut msg = self.id.default_message().to_string();
for (i, arg) in self.args.iter().enumerate() {
let placeholder = format!("%{}", i);
msg = msg.replace(&placeholder, arg);
}
for (i, arg) in self.args.iter().enumerate() {
if !msg.contains(&format!("%{}", i)) {
msg.push(' ');
msg.push_str(arg);
}
}
msg
} else {
self.message.clone()
};
let mut diag =
StructuredDiagnostic::new(self.id, self.severity, final_message, self.location.clone());
diag.ranges = std::mem::take(&mut self.ranges);
diag.notes = std::mem::take(&mut self.notes);
diag.fixits = std::mem::take(&mut self.fixits);
if self.engine.ignore_warnings && diag.severity == DiagSeverity::Warning {
return;
}
if self.engine.warnings_as_errors && diag.severity == DiagSeverity::Warning {
diag.severity = DiagSeverity::Error;
}
match diag.severity {
DiagSeverity::Error | DiagSeverity::Fatal => {
self.engine.error_count += 1;
if self.engine.error_count >= self.engine.error_limit
&& diag.severity == DiagSeverity::Error
{
diag.severity = DiagSeverity::Fatal;
}
}
DiagSeverity::Warning => {
self.engine.warning_count += 1;
}
_ => {}
}
for consumer in &mut self.engine.consumers {
consumer.begin_diagnostic(&diag);
for note in &diag.notes {
consumer.handle_note(note);
}
for range in &diag.ranges {
consumer.handle_range(range);
}
for fixit in &diag.fixits {
consumer.handle_fixit(fixit);
}
consumer.end_diagnostic();
}
if diag.severity == DiagSeverity::Fatal {
if let Some(ref handler) = self.engine.fatal_handler {
handler(&diag);
}
}
}
}
pub struct TextDiagnosticPrinter<W: Write> {
writer: W,
format: DiagnosticOutputFormat,
color: ColorMode,
show_column: bool,
show_location: bool,
show_carets: bool,
show_fixits: bool,
show_source_ranges: bool,
tab_stop: u32,
message_length: u32,
error_count: usize,
warning_count: usize,
current_diag: Option<StructuredDiagnostic>,
}
impl<W: Write> TextDiagnosticPrinter<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
format: DiagnosticOutputFormat::Clang,
color: ColorMode::Auto,
show_column: true,
show_location: true,
show_carets: true,
show_fixits: true,
show_source_ranges: true,
tab_stop: 8,
message_length: 0,
error_count: 0,
warning_count: 0,
current_diag: None,
}
}
fn write_header(&mut self, diag: &StructuredDiagnostic) -> io::Result<()> {
match self.format {
DiagnosticOutputFormat::Clang | DiagnosticOutputFormat::GCC => {
write!(
self.writer,
"{}:{}:{}: {}: {}\n",
diag.location.file.display(),
diag.location.line,
diag.location.column,
diag.severity.as_str(),
diag.message
)?;
if let Some(ref flag) = diag.flag_name {
writeln!(self.writer, " {}", flag)?;
}
}
DiagnosticOutputFormat::MSVC => {
write!(
self.writer,
"{}({}): {} C{:04}: {}\n",
diag.location.file.display(),
diag.location.line,
if diag.severity.is_error() {
"error"
} else {
"warning"
},
diag.id as u32 % 10000,
diag.message
)?;
}
DiagnosticOutputFormat::Vi => {
write!(
self.writer,
"{} +{} {}: {}\n",
diag.location.file.display(),
diag.location.line,
diag.severity.as_str(),
diag.message
)?;
}
_ => {
writeln!(self.writer, "{}: {}", diag.severity.as_str(), diag.message)?;
}
}
Ok(())
}
fn write_caret_line(
&mut self,
diag: &StructuredDiagnostic,
source_manager: &ClangSourceManager,
) -> io::Result<()> {
if !self.show_carets || !diag.location.is_valid {
return Ok(());
}
let source_line = source_manager.get_line(&diag.location.file, diag.location.line);
if let Some(line) = source_line {
writeln!(self.writer, " {} | {}", diag.location.line, line)?;
let line_num_width = diag.location.line.to_string().len();
let padding = " ".repeat(line_num_width + 3);
let col = diag.location.column as usize;
let caret_indent = if col > 1 { col - 1 } else { 0 };
write!(self.writer, "{}", padding)?;
if caret_indent > 0 {
write!(self.writer, "{}", " ".repeat(caret_indent))?;
}
if self.show_source_ranges {
writeln!(
self.writer,
"{}",
self.build_caret_string(diag, &line, caret_indent)
)?;
} else {
writeln!(self.writer, "^")?;
}
}
Ok(())
}
fn build_caret_string(
&self,
diag: &StructuredDiagnostic,
line: &str,
caret_offset: usize,
) -> String {
let mut result = String::new();
let mut has_range = false;
let diag_line = diag.location.line;
for range in &diag.ranges {
if range.begin.line == diag_line
&& range.end.line == diag_line
&& range.begin.file == diag.location.file
{
has_range = true;
let start_col = range.begin.column as usize;
let end_col = range.end.column as usize;
if start_col > 1 {
let leading = start_col - 1;
if leading > caret_offset {
result.push_str(&" ".repeat(leading - caret_offset));
}
}
let range_len = if end_col >= start_col {
end_col - start_col + 1
} else {
1
};
if range_len > 2 {
result.push('~');
if range_len > 3 {
result.push_str(&"~".repeat(range_len - 2));
}
result.push('~');
} else if range_len == 2 {
result.push_str("~~");
} else {
if start_col > caret_offset + 1 {
result.push_str(&" ".repeat(start_col - caret_offset - 1));
}
result.push('^');
}
}
}
if !has_range {
result.push('^');
} else {
if result.is_empty() {
result.push('^');
}
}
result
}
fn write_fixits(
&mut self,
diag: &StructuredDiagnostic,
source_manager: &ClangSourceManager,
) -> io::Result<()> {
if !self.show_fixits || diag.fixits.is_empty() {
return Ok(());
}
for fixit in &diag.fixits {
let line_num = fixit.remove_range.begin.line;
let source_line = source_manager.get_line(&fixit.remove_range.begin.file, line_num);
if let Some(line) = source_line {
let line_num_width = line_num.to_string().len();
let padding = " ".repeat(line_num_width + 3);
if fixit.is_insertion {
write!(self.writer, "{}+{}", padding, line_num)?;
writeln!(self.writer)?;
let col = fixit.remove_range.begin.column as usize;
if col > 1 {
write!(self.writer, "{}", " ".repeat(col - 1))?;
}
writeln!(self.writer, "+{}", fixit.code)?;
} else if fixit.code.is_empty() {
writeln!(self.writer, "{}-{}", padding, line_num)?;
let col = fixit.remove_range.begin.column as usize;
if col > 1 {
write!(self.writer, "{}", " ".repeat(col - 1))?;
}
let end_col = fixit.remove_range.end.column as usize;
let len = if end_col > col { end_col - col } else { 1 };
writeln!(self.writer, "-{}", "-".repeat(len))?;
} else {
writeln!(self.writer, "{}-{}", padding, line_num)?;
let col = fixit.remove_range.begin.column as usize;
if col > 1 {
write!(self.writer, "{}", " ".repeat(col - 1))?;
}
let end_col = fixit.remove_range.end.column as usize;
let len = if end_col > col { end_col - col } else { 1 };
writeln!(
self.writer,
"{}\n{}+{}",
"-".repeat(len),
&padding[1..],
fixit.code
)?;
}
}
}
writeln!(self.writer)?;
Ok(())
}
}
impl<W: Write + fmt::Debug> fmt::Debug for TextDiagnosticPrinter<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TextDiagnosticPrinter")
.field("format", &self.format)
.field("color", &self.color)
.field("error_count", &self.error_count)
.field("warning_count", &self.warning_count)
.finish()
}
}
impl<W: Write> DiagnosticConsumer for TextDiagnosticPrinter<W> {
fn begin_diagnostic(&mut self, diag: &StructuredDiagnostic) {
self.current_diag = Some(diag.clone());
}
fn end_diagnostic(&mut self) {
if self.current_diag.is_some() {
let diag = self.current_diag.take().unwrap();
let _ = self.write_header(&diag);
let _ = self.write_fixits(&diag, &ClangSourceManager::default());
let _ = self.writer.flush();
}
}
fn handle_note(&mut self, note: &DiagnosticNote) {
let _ = writeln!(
self.writer,
"{}:{}:{}: note: {}",
note.location.file.display(),
note.location.line,
note.location.column,
note.message
);
}
fn handle_fixit(&mut self, _fixit: &FixItHint) {}
fn handle_range(&mut self, _range: &SourceRange) {}
fn flush(&mut self) {
let _ = self.writer.flush();
}
fn error_count(&self) -> usize {
self.error_count
}
fn warning_count(&self) -> usize {
self.warning_count
}
fn clear(&mut self) {
self.error_count = 0;
self.warning_count = 0;
self.current_diag = None;
}
}
pub struct SerializedDiagnosticPrinter<W: Write> {
writer: W,
error_count: usize,
warning_count: usize,
}
impl<W: Write> SerializedDiagnosticPrinter<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
error_count: 0,
warning_count: 0,
}
}
fn write_string(&mut self, s: &str) -> io::Result<()> {
let bytes = s.as_bytes();
let len = bytes.len() as u32;
self.writer.write_all(&len.to_le_bytes())?;
self.writer.write_all(bytes)?;
Ok(())
}
fn write_record(&mut self, diag: &StructuredDiagnostic) -> io::Result<()> {
self.writer.write_all(b"DIAG")?;
let id = diag.id as u32;
self.writer.write_all(&id.to_le_bytes())?;
let severity_byte = match diag.severity {
DiagSeverity::Ignored => 0u8,
DiagSeverity::Remark => 1u8,
DiagSeverity::Note => 2u8,
DiagSeverity::Warning => 3u8,
DiagSeverity::Error => 4u8,
DiagSeverity::Fatal => 5u8,
};
self.writer.write_all(&[severity_byte])?;
self.write_string(&diag.message)?;
self.write_string(&diag.location.file.display().to_string())?;
self.writer.write_all(&diag.location.line.to_le_bytes())?;
self.writer.write_all(&diag.location.column.to_le_bytes())?;
let notes_len = diag.notes.len() as u32;
self.writer.write_all(¬es_len.to_le_bytes())?;
for note in &diag.notes {
self.write_string(¬e.message)?;
self.write_string(¬e.location.file.display().to_string())?;
self.writer.write_all(¬e.location.line.to_le_bytes())?;
self.writer.write_all(¬e.location.column.to_le_bytes())?;
}
let fixits_len = diag.fixits.len() as u32;
self.writer.write_all(&fixits_len.to_le_bytes())?;
for fixit in &diag.fixits {
let is_insert = if fixit.is_insertion { 1u8 } else { 0u8 };
self.writer.write_all(&[is_insert])?;
self.write_string(&fixit.code)?;
self.writer
.write_all(&fixit.remove_range.begin.line.to_le_bytes())?;
self.writer
.write_all(&fixit.remove_range.begin.column.to_le_bytes())?;
self.writer
.write_all(&fixit.remove_range.end.line.to_le_bytes())?;
self.writer
.write_all(&fixit.remove_range.end.column.to_le_bytes())?;
}
let ranges_len = diag.ranges.len() as u32;
self.writer.write_all(&ranges_len.to_le_bytes())?;
for range in &diag.ranges {
self.writer.write_all(&range.begin.line.to_le_bytes())?;
self.writer.write_all(&range.begin.column.to_le_bytes())?;
self.writer.write_all(&range.end.line.to_le_bytes())?;
self.writer.write_all(&range.end.column.to_le_bytes())?;
}
if let Some(ref flag) = diag.flag_name {
self.writer.write_all(&[1u8])?;
self.write_string(flag)?;
} else {
self.writer.write_all(&[0u8])?;
}
Ok(())
}
}
impl<W: Write + fmt::Debug> fmt::Debug for SerializedDiagnosticPrinter<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SerializedDiagnosticPrinter")
.field("error_count", &self.error_count)
.field("warning_count", &self.warning_count)
.finish()
}
}
impl<W: Write> DiagnosticConsumer for SerializedDiagnosticPrinter<W> {
fn begin_diagnostic(&mut self, diag: &StructuredDiagnostic) {
let _ = self.write_record(diag);
}
fn end_diagnostic(&mut self) {}
fn handle_note(&mut self, _note: &DiagnosticNote) {}
fn handle_fixit(&mut self, _fixit: &FixItHint) {}
fn handle_range(&mut self, _range: &SourceRange) {}
fn flush(&mut self) {
let _ = self.writer.flush();
}
fn error_count(&self) -> usize {
self.error_count
}
fn warning_count(&self) -> usize {
self.warning_count
}
fn clear(&mut self) {
self.error_count = 0;
self.warning_count = 0;
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticOptions {
pub ignore_warnings: bool,
pub warnings_as_errors: bool,
pub show_colors: bool,
pub show_column: bool,
pub show_location: bool,
pub show_carets: bool,
pub show_fixits: bool,
pub show_source_ranges: bool,
pub tab_stop: u32,
pub message_length: u32,
pub show_categories: bool,
pub show_option_names: bool,
pub remark_enabled: bool,
pub error_limit: usize,
pub template_backtrace_limit: u32,
pub macro_backtrace_limit: u32,
pub fixit_colors: bool,
}
impl Default for DiagnosticOptions {
fn default() -> Self {
Self {
ignore_warnings: false,
warnings_as_errors: false,
show_colors: false,
show_column: true,
show_location: true,
show_carets: true,
show_fixits: true,
show_source_ranges: true,
tab_stop: 8,
message_length: 0,
show_categories: false,
show_option_names: false,
remark_enabled: false,
error_limit: 20,
template_backtrace_limit: 10,
macro_backtrace_limit: 10,
fixit_colors: false,
}
}
}
impl DiagnosticOptions {
pub fn for_terminal() -> Self {
Self {
show_colors: true,
show_column: true,
show_location: true,
show_carets: true,
show_fixits: true,
show_source_ranges: true,
fixit_colors: true,
..Default::default()
}
}
pub fn for_ide() -> Self {
Self {
show_colors: false,
show_column: true,
show_location: true,
show_carets: false,
show_fixits: true,
show_source_ranges: false,
show_option_names: true,
..Default::default()
}
}
pub fn for_machine() -> Self {
Self {
show_colors: false,
show_column: true,
show_location: true,
show_carets: false,
show_fixits: false,
show_source_ranges: false,
show_categories: false,
..Default::default()
}
}
}
#[derive(Debug)]
pub struct DiagnosticRenderer {
options: DiagnosticOptions,
}
impl DiagnosticRenderer {
pub fn new(options: DiagnosticOptions) -> Self {
Self { options }
}
pub fn render_caret(&self, line: &str, column: u32, ranges: &[SourceRange]) -> String {
let col = column as usize;
let mut caret = String::new();
if col > 1 {
caret.push_str(&" ".repeat(col - 1));
}
caret.push('^');
for range in ranges {
if range.begin.line == range.end.line && range.begin.column != range.end.column {
let start = range.begin.column.min(range.end.column) as usize;
let end = range.begin.column.max(range.end.column) as usize;
let len = end - start;
if len > 0 && start < line.len() + 1 {
let tilde_start = start.max(col);
let tilde_end = end.min(line.len() + 1);
if tilde_end > tilde_start {
let needed_len = tilde_end - 1;
while caret.len() < needed_len {
caret.push('~');
}
}
}
}
}
caret
}
pub fn render_snippet(
&self,
source_manager: &ClangSourceManager,
location: &ClangSourceLocation,
context_lines: u32,
) -> String {
let mut output = String::new();
let start_line = if location.line > context_lines {
location.line - context_lines
} else {
1
};
let line_num_width = (location.line + context_lines).to_string().len();
for l in start_line..=location.line + context_lines {
let source_line = source_manager.get_line(&location.file, l);
if let Some(line) = source_line {
let prefix = if l == location.line { ">" } else { " " };
output.push_str(&format!(
"{}{:>width$} | {}\n",
prefix,
l,
line,
width = line_num_width
));
}
}
output
}
pub fn render_fixits(
&self,
fixits: &[FixItHint],
source_manager: &ClangSourceManager,
) -> String {
if !self.options.show_fixits || fixits.is_empty() {
return String::new();
}
let mut output = String::new();
for fixit in fixits {
let loc = &fixit.remove_range.begin;
let source_line = source_manager.get_line(&loc.file, loc.line);
if let Some(_line) = source_line {
if fixit.is_insertion {
output.push_str(&format!(
" fix-it:\"{}:{}-{}\": insert \"{}\"\n",
loc.file.display(),
loc.line,
loc.column,
fixit.code
));
} else if fixit.code.is_empty() {
output.push_str(&format!(
" fix-it:\"{}:{}-{}:{}\": remove\n",
loc.file.display(),
loc.line,
fixit.remove_range.end.line,
fixit.remove_range.end.column,
));
} else {
output.push_str(&format!(
" fix-it:\"{}:{}-{}:{}\": replace with \"{}\"\n",
loc.file.display(),
loc.line,
fixit.remove_range.end.line,
fixit.remove_range.end.column,
fixit.code
));
}
}
}
output
}
pub fn render_diagnostic(
&self,
diag: &StructuredDiagnostic,
source_manager: &ClangSourceManager,
) -> String {
let mut output = String::new();
let sev_str = diag.severity.as_str();
if self.options.show_location {
output.push_str(&format!(
"{}:{}:{}: {}: {}\n",
diag.location.file.display(),
diag.location.line,
diag.location.column,
sev_str,
diag.message
));
} else {
output.push_str(&format!("{}: {}\n", sev_str, diag.message));
}
if self.options.show_option_names {
if let Some(ref flag) = diag.flag_name {
output.push_str(&format!(" {}\n", flag));
}
}
if self.options.show_categories {
output.push_str(&format!(" [{}]\n", diag.category));
}
if self.options.show_carets && diag.location.is_valid {
let source_line = source_manager.get_line(&diag.location.file, diag.location.line);
if let Some(line) = source_line {
let line_num_width = diag.location.line.to_string().len();
output.push_str(&format!(
" {:>width$} | {}\n",
diag.location.line,
line,
width = line_num_width
));
output.push_str(&format!(
" {:>width$} | {}\n",
"",
self.render_caret(&line, diag.location.column, &diag.ranges),
width = line_num_width
));
}
}
if self.options.show_source_ranges {
for range in &diag.ranges {
if range.is_valid() && range.is_single_line() {
let source_line = source_manager.get_line(&range.begin.file, range.begin.line);
if let Some(line) = source_line {
let start_col = range.begin.column.min(range.end.column) as usize;
let end_col = range.begin.column.max(range.end.column) as usize;
let len = end_col.saturating_sub(start_col).max(1);
let line_num_width = range.begin.line.to_string().len();
output.push_str(&format!(
" {:>width$} | {}{}\n",
"",
" ".repeat(start_col.saturating_sub(1)),
"~".repeat(len),
width = line_num_width
));
}
}
}
}
if self.options.show_fixits {
let fixit_text = self.render_fixits(&diag.fixits, source_manager);
if !fixit_text.is_empty() {
output.push_str(&fixit_text);
}
}
for note in &diag.notes {
if self.options.show_location {
output.push_str(&format!(
"{}:{}:{}: note: {}\n",
note.location.file.display(),
note.location.line,
note.location.column,
note.message
));
} else {
output.push_str(&format!("note: {}\n", note.message));
}
}
output
}
}
#[derive(Debug, Clone)]
pub struct WarningGroup {
pub name: String,
pub members: Vec<String>,
pub enabled_by_default: bool,
}
#[derive(Debug, Clone)]
pub struct WarningGroupRegistry {
pub groups: HashMap<String, WarningGroup>,
}
impl WarningGroupRegistry {
pub fn new() -> Self {
let mut registry = Self {
groups: HashMap::new(),
};
registry.register_default_groups();
registry
}
fn register_default_groups(&mut self) {
self.add_group(
"Wall",
&[
"Waddress",
"Warray-bounds",
"Wbool-compare",
"Wbool-operation",
"Wchar-subscripts",
"Wcomment",
"Wdangling-else",
"Wenum-compare",
"Wformat",
"Wimplicit",
"Wimplicit-int",
"Wimplicit-function-declaration",
"Winit-self",
"Wint-in-bool-context",
"Wlogical-not-parentheses",
"Wmain",
"Wmaybe-uninitialized",
"Wmemset-transposed-args",
"Wmisleading-indentation",
"Wmissing-braces",
"Wnarrowing",
"Wnonnull",
"Wopenmp-simd",
"Wparentheses",
"Wpessimizing-move",
"Wpointer-sign",
"Wreorder",
"Wreturn-type",
"Wsequence-point",
"Wsign-compare",
"Wsizeof-pointer-div",
"Wsizeof-pointer-memaccess",
"Wstrict-aliasing",
"Wstrict-overflow",
"Wswitch",
"Wtautological-compare",
"Wtrigraphs",
"Wuninitialized",
"Wunknown-pragmas",
"Wunused",
"Wunused-function",
"Wunused-label",
"Wunused-value",
"Wunused-variable",
"Wvolatile-register-var",
],
false,
);
self.add_group(
"Wextra",
&[
"Wall",
"Wcast-function-type",
"Wclobbered",
"Wempty-body",
"Wignored-qualifiers",
"Wimplicit-fallthrough",
"Wmissing-field-initializers",
"Wmissing-parameter-type",
"Wold-style-declaration",
"Woverride-init",
"Wsign-compare",
"Wtype-limits",
"Wshift-negative-value",
"Wunused-but-set-parameter",
"Wunused-but-set-variable",
"Wunused-parameter",
],
false,
);
self.add_group(
"Wpedantic",
&[
"Wlong-long",
"Wvariadic-macros",
"Woverlength-strings",
"Wc99-extensions",
"Wc11-extensions",
"Wgnu",
"Wgnu-zero-variadic-macro-arguments",
"Wc++-compat",
"Wc99-c11-compat",
"Wc11-c2x-compat",
"Wnewline-eof",
"Wzero-length-array",
],
false,
);
self.add_group(
"Wconversion",
&[
"Wconversion",
"Wfloat-conversion",
"Wsign-conversion",
"Wshorten-64-to-32",
"Wimplicit-int-conversion",
"Wimplicit-float-conversion",
"Wbitfield-conversion",
"Wenum-conversion",
"Wnullable-to-nonnull-conversion",
],
false,
);
self.add_group(
"Wshadow",
&[
"Wshadow",
"Wshadow-field",
"Wshadow-ivar",
"Wshadow-all",
"Wshadow-uncaptured-local",
],
false,
);
self.add_group(
"Wunused",
&[
"Wunused-variable",
"Wunused-parameter",
"Wunused-function",
"Wunused-label",
"Wunused-value",
"Wunused-result",
"Wunused-local-typedef",
"Wunused-private-field",
"Wunused-const-variable",
"Wunused-macros",
],
true,
);
self.add_group(
"Wformat",
&[
"Wformat",
"Wformat-security",
"Wformat-extra-args",
"Wformat-zero-length",
"Wformat-nonliteral",
"Wformat-contains-nul",
"Wformat-invalid-specifier",
"Wformat-overflow",
"Wformat-y2k",
],
true,
);
self.add_group("Wimplicit-fallthrough", &["Wimplicit-fallthrough"], false);
self.add_group(
"Wstrict-aliasing",
&[
"Wstrict-aliasing",
"Wstrict-aliasing=0",
"Wstrict-aliasing=1",
"Wstrict-aliasing=2",
],
true,
);
self.add_group("Wreturn-type", &["Wreturn-type"], true);
self.add_group(
"Wuninitialized",
&[
"Wuninitialized",
"Wconditional-uninitialized",
"Wmaybe-uninitialized",
],
true,
);
self.add_group(
"Wparentheses",
&[
"Wparentheses",
"Wparentheses-equality",
"Wlogical-op-parentheses",
"Wshift-op-parentheses",
"Wdangling-else",
],
false,
);
self.add_group("Wsign-compare", &["Wsign-compare"], false);
self.add_group(
"Wswitch",
&["Wswitch", "Wswitch-enum", "Wswitch-default"],
true,
);
self.add_group(
"Wtautological-compare",
&[
"Wtautological-compare",
"Wtautological-constant-compare",
"Wtautological-pointer-compare",
"Wtautological-overlap-compare",
"Wtautological-type-limit-compare",
"Wtautological-out-of-range",
],
true,
);
}
fn add_group(&mut self, name: &str, members: &[&str], enabled_by_default: bool) {
self.groups.insert(
name.to_string(),
WarningGroup {
name: name.to_string(),
members: members.iter().map(|s| s.to_string()).collect(),
enabled_by_default,
},
);
}
pub fn resolve_group(&self, name: &str) -> Vec<String> {
let mut result = Vec::new();
let mut visited = HashSet::new();
self.resolve_group_recursive(name, &mut result, &mut visited);
result
}
fn resolve_group_recursive(
&self,
name: &str,
result: &mut Vec<String>,
visited: &mut HashSet<String>,
) {
if !visited.insert(name.to_string()) {
return;
}
if let Some(group) = self.groups.get(name) {
for member in &group.members {
if self.groups.contains_key(member) {
self.resolve_group_recursive(member, result, visited);
} else {
result.push(member.clone());
}
}
} else {
result.push(name.to_string());
}
}
pub fn has_group(&self, name: &str) -> bool {
self.groups.contains_key(name)
}
pub fn group_names(&self) -> Vec<String> {
self.groups.keys().cloned().collect()
}
}
impl Default for WarningGroupRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn get_error_message(id: DiagID) -> &'static str {
match id {
DiagID::ErrExpectedSemiAfterExpr => "expected ';' after expression",
DiagID::ErrExpectedSemiAfterDecl => "expected ';' after declaration",
DiagID::ErrExpectedExpression => "expected expression",
DiagID::ErrExpectedType => "expected a type",
DiagID::ErrExpectedIdentifier => "expected unqualified-id",
DiagID::ErrExpectedRBrace => "expected '}'",
DiagID::ErrExpectedRParen => "expected ')'",
DiagID::ErrExpectedRBracket => "expected ']'",
DiagID::ErrExpectedComma => "expected ','",
DiagID::ErrExpectedFuncBody => "expected function body after function declarator",
DiagID::ErrUndeclaredIdentifier => "use of undeclared identifier '%0'",
DiagID::ErrUndeclaredVarUse => "use of undeclared identifier '%0'",
DiagID::ErrRedefinition => "redefinition of '%0'",
DiagID::ErrRedefinitionDifferentType => {
"redefinition of '%0' with a different type: %1 vs %2"
}
DiagID::ErrCannotInitialize => {
"cannot initialize a variable of type '%0' with an lvalue of type '%1'"
}
DiagID::ErrCannotInitializeWithInitList => "cannot initialize %0 with an initializer list",
DiagID::ErrCannotConvert => "cannot convert '%0' to '%1'",
DiagID::ErrCannotConvertToPointer => "cannot convert '%0' to pointer type '%1'",
DiagID::ErrIncompatibleTypes => "incompatible operand types ('%0' and '%1')",
DiagID::ErrIncompatiblePointerTypes => {
"incompatible pointer types assigning to '%0' from '%1'"
}
DiagID::ErrNoMatchingFunction => "no matching function for call to '%0'",
DiagID::ErrNoMatchingMemberFunction => "no matching member function for call to '%0'",
DiagID::ErrNoMatchingConstructor => "no matching constructor for initialization of '%0'",
DiagID::ErrTooManyArgs => "too many arguments to function call, expected %0, have %1",
DiagID::ErrTooFewArgs => "too few arguments to function call, expected %0, have %1",
DiagID::ErrCallToDeleted => "call to deleted function '%0'",
DiagID::ErrCallToInaccessible => "%0 is a private member of %1",
DiagID::ErrUnaryAmpersandOnTemporary => {
"taking the address of a temporary object of type '%0'"
}
DiagID::ErrDivisionByZeroConst => "division by zero is undefined",
DiagID::ErrShiftByNegative => "shift count is negative",
DiagID::ErrAssignmentToConst => {
"cannot assign to variable '%0' with const-qualified type '%1'"
}
DiagID::ErrAbstractClass => "allocating an object of abstract class type '%0'",
DiagID::ErrPrivateMember => "'%0' is a private member of '%1'",
DiagID::ErrProtectedMember => "'%0' is a protected member of '%1'",
DiagID::ErrInvalidUseOfThis => {
"invalid use of 'this' outside of a non-static member function"
}
DiagID::ErrInvalidUseOfIncompleteType => "invalid use of incomplete type '%0'",
DiagID::ErrSizeOfIncompleteType => {
"invalid application of 'sizeof' to an incomplete type '%0'"
}
DiagID::ErrDeleteOfIncompleteType => {
"deleting pointer to incomplete type '%0' may cause undefined behavior"
}
DiagID::ErrFieldOfIncompleteType => "field has incomplete type '%0'",
DiagID::ErrPPIncludeNotFound => "'%0' file not found",
DiagID::ErrPPMacroRedefined => "macro '%0' redefined",
DiagID::ErrPPBadPaste => "pasting formed '%0', an invalid preprocessing token",
DiagID::ErrPPUnterminatedComment => "unterminated /* comment",
DiagID::ErrStaticAssertFailed => "static assertion failed due to requirement '%0': %1",
DiagID::ErrNegativeArraySize => "array size is negative",
DiagID::ErrDuplicateMember => "duplicate member '%0'",
DiagID::ErrFlexibleArrayMember => {
"flexible array member '%0' not allowed in otherwise empty struct"
}
_ => id.default_message(),
}
}
pub struct ErrorMessageCatalog;
impl ErrorMessageCatalog {
pub fn expected_semi_after_expression(file: &str, line: u32, col: u32) -> String {
format!(
"{}:{}:{}: error: expected ';' after expression\n\
{:>width$} | {}\n\
{:>width$} | {}\n",
file,
line,
col,
"",
"_expression_",
"",
" ^",
width = line.to_string().len()
)
}
pub fn use_of_undeclared_identifier(
file: &str,
line: u32,
col: u32,
name: &str,
suggestion: Option<&str>,
) -> String {
let mut msg = format!(
"{}:{}:{}: error: use of undeclared identifier '{}'\n",
file, line, col, name
);
if let Some(sug) = suggestion {
msg.push_str(&format!("did you mean '{}'?\n", sug));
}
msg
}
pub fn redefinition(
file: &str,
line: u32,
col: u32,
name: &str,
prev_file: &str,
prev_line: u32,
prev_col: u32,
) -> String {
format!(
"{}:{}:{}: error: redefinition of '{}'\n\
{}:{}:{}: note: previous definition is here\n",
file, line, col, name, prev_file, prev_line, prev_col
)
}
pub fn cannot_init_with_init_list(file: &str, line: u32, col: u32, ty: &str) -> String {
format!(
"{}:{}:{}: error: cannot initialize '{}' with an initializer list\n",
file, line, col, ty
)
}
pub fn no_matching_function(
file: &str,
line: u32,
col: u32,
func: &str,
candidates: &[String],
) -> String {
let mut msg = format!(
"{}:{}:{}: error: no matching function for call to '{}'\n",
file, line, col, func
);
for candidate in candidates {
msg.push_str(&format!(
"note: candidate function not viable: {}\n",
candidate
));
}
msg
}
pub fn cannot_convert(file: &str, line: u32, col: u32, from: &str, to: &str) -> String {
format!(
"{}:{}:{}: error: cannot convert '{}' to '{}'\n",
file, line, col, from, to
)
}
pub fn address_of_temporary(file: &str, line: u32, col: u32, ty: &str) -> String {
format!(
"{}:{}:{}: error: taking the address of a temporary object of type '{}'\n",
file, line, col, ty
)
}
pub fn division_by_zero(file: &str, line: u32, col: u32) -> String {
format!(
"{}:{}:{}: warning: division by zero is undefined [-Wdivision-by-zero]\n",
file, line, col
)
}
pub fn sign_compare(file: &str, line: u32, col: u32, type1: &str, type2: &str) -> String {
format!(
"{}:{}:{}: warning: comparison of integers of different signs: '{}' and '{}' [-Wsign-compare]\n",
file, line, col, type1, type2
)
}
pub fn unused_variable(file: &str, line: u32, col: u32, name: &str) -> String {
format!(
"{}:{}:{}: warning: unused variable '{}' [-Wunused-variable]\n",
file, line, col, name
)
}
pub fn unused_parameter(file: &str, line: u32, col: u32, name: &str) -> String {
format!(
"{}:{}:{}: warning: unused parameter '{}' [-Wunused-parameter]\n",
file, line, col, name
)
}
pub fn shadow_declaration(file: &str, line: u32, col: u32, name: &str) -> String {
format!(
"{}:{}:{}: warning: declaration shadows a local variable [-Wshadow]\n",
file, line, col
)
}
pub fn implicit_function_decl(file: &str, line: u32, col: u32, name: &str) -> String {
format!(
"{}:{}:{}: warning: implicit declaration of function '{}' [-Wimplicit-function-declaration]\n",
file, line, col, name
)
}
pub fn int_conversion(file: &str, line: u32, col: u32, from: &str, to: &str) -> String {
format!(
"{}:{}:{}: warning: implicit conversion from '{}' to '{}' changes value [-Wint-conversion]\n",
file, line, col, from, to
)
}
pub fn tautological_compare(file: &str, line: u32, col: u32) -> String {
format!(
"{}:{}:{}: warning: comparison is always true/false [-Wtautological-compare]\n",
file, line, col
)
}
pub fn format_string(file: &str, line: u32, col: u32, msg: &str) -> String {
format!(
"{}:{}:{}: warning: format string issue: {} [-Wformat]\n",
file, line, col, msg
)
}
pub fn expected_semi_after(file: &str, line: u32, col: u32, before: &str) -> String {
format!(
"{}:{}:{}: error: expected ';' after {} [expected-semi]\n",
file, line, col, before
)
}
pub fn to_match_paren(file: &str, line: u32, col: u32) -> String {
format!("{}:{}:{}: note: to match this '('\n", file, line, col)
}
pub fn in_instantiation_of(file: &str, line: u32, col: u32, template: &str) -> String {
format!(
"{}:{}:{}: note: in instantiation of template class '{}' requested here\n",
file, line, col, template
)
}
pub fn required_from_here(file: &str, line: u32, col: u32) -> String {
format!("{}:{}:{}: note: required from here\n", file, line, col)
}
pub fn candidate_not_viable(
file: &str,
line: u32,
col: u32,
func_name: &str,
reason: &str,
) -> String {
format!(
"{}:{}:{}: note: candidate function '{}' not viable: {}\n",
file, line, col, func_name, reason
)
}
pub fn forward_declaration_of(file: &str, line: u32, col: u32, name: &str) -> String {
format!(
"{}:{}:{}: note: forward declaration of '{}'\n",
file, line, col, name
)
}
pub fn previous_declaration(file: &str, line: u32, col: u32) -> String {
format!(
"{}:{}:{}: note: previous declaration is here\n",
file, line, col
)
}
pub fn in_expansion_of_macro(file: &str, line: u32, col: u32, macro_name: &str) -> String {
format!(
"{}:{}:{}: note: in expansion of macro '{}'\n",
file, line, col, macro_name
)
}
pub fn expected_brace_match(file: &str, line: u32, col: u32) -> String {
format!(
"{}:{}:{}: error: expected '}}' to match this '{{'\n",
file, line, col
)
}
pub fn control_reaches_end_non_void(file: &str, line: u32, col: u32) -> String {
format!(
"{}:{}:{}: warning: control reaches end of non-void function [-Wreturn-type]\n",
file, line, col
)
}
}
#[derive(Debug, Clone)]
pub struct FixItFactory;
impl FixItFactory {
pub fn insert_semicolon(loc: &ClangSourceLocation) -> FixItHint {
FixItHint::insertion(loc.clone(), ";")
}
pub fn insert_rbrace(loc: &ClangSourceLocation) -> FixItHint {
FixItHint::insertion(loc.clone(), "}")
}
pub fn insert_rparen(loc: &ClangSourceLocation) -> FixItHint {
FixItHint::insertion(loc.clone(), ")")
}
pub fn replace_identifier(range: SourceRange, replacement: &str) -> FixItHint {
FixItHint::replacement(range, replacement)
}
pub fn remove(range: SourceRange) -> FixItHint {
FixItHint::removal(range)
}
pub fn insert_missing_include(loc: &ClangSourceLocation, header: &str) -> FixItHint {
let code = format!("#include <{}>\n", header);
FixItHint::insertion(loc.clone(), &code)
}
pub fn replace_assign_with_compare(range: SourceRange) -> FixItHint {
FixItHint::replacement(range, "==")
}
pub fn insert_static(loc: &ClangSourceLocation) -> FixItHint {
FixItHint::insertion(loc.clone(), "static ")
}
pub fn insert_const(loc: &ClangSourceLocation) -> FixItHint {
FixItHint::insertion(loc.clone(), "const ")
}
pub fn wrap_in_parens(range: SourceRange) -> FixItHint {
let code = "( ... )".to_string();
FixItHint::replacement(range, &code)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TermColor {
Bold,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Reset,
}
impl TermColor {
pub fn ansi_code(&self) -> &'static str {
match self {
Self::Bold => "\x1b[1m",
Self::Red => "\x1b[31m",
Self::Green => "\x1b[32m",
Self::Yellow => "\x1b[33m",
Self::Blue => "\x1b[34m",
Self::Magenta => "\x1b[35m",
Self::Cyan => "\x1b[36m",
Self::White => "\x1b[37m",
Self::Reset => "\x1b[0m",
}
}
}
#[derive(Debug, Clone)]
pub struct DiagColorScheme {
pub use_colors: bool,
pub error_color: Vec<TermColor>,
pub warning_color: Vec<TermColor>,
pub note_color: Vec<TermColor>,
pub fixit_color: Vec<TermColor>,
pub caret_color: Vec<TermColor>,
pub source_color: Vec<TermColor>,
pub filename_color: Vec<TermColor>,
pub line_number_color: Vec<TermColor>,
pub range_color: Vec<TermColor>,
}
impl DiagColorScheme {
pub fn clang_style() -> Self {
Self {
use_colors: true,
error_color: vec![TermColor::Bold, TermColor::Red],
warning_color: vec![TermColor::Bold, TermColor::Magenta],
note_color: vec![TermColor::Bold, TermColor::Cyan],
fixit_color: vec![TermColor::Bold, TermColor::Green],
caret_color: vec![TermColor::Bold, TermColor::Green],
source_color: vec![TermColor::Reset],
filename_color: vec![TermColor::Bold],
line_number_color: vec![TermColor::Bold],
range_color: vec![TermColor::Green],
}
}
pub fn monochrome() -> Self {
Self {
use_colors: false,
error_color: vec![],
warning_color: vec![],
note_color: vec![],
fixit_color: vec![],
caret_color: vec![],
source_color: vec![],
filename_color: vec![],
line_number_color: vec![],
range_color: vec![],
}
}
pub fn error_open(&self) -> String {
Self::sequence(&self.error_color)
}
pub fn warning_open(&self) -> String {
Self::sequence(&self.warning_color)
}
pub fn note_open(&self) -> String {
Self::sequence(&self.note_color)
}
pub fn fixit_open(&self) -> String {
Self::sequence(&self.fixit_color)
}
pub fn close(&self) -> String {
Self::sequence(&[TermColor::Reset])
}
fn sequence(cols: &[TermColor]) -> String {
if cols.is_empty() {
return String::new();
}
cols.iter()
.map(|c| c.ansi_code())
.collect::<Vec<_>>()
.concat()
}
pub fn colorize(&self, severity: DiagSeverity, text: &str) -> String {
if !self.use_colors {
return text.to_string();
}
let (open, close) = match severity {
DiagSeverity::Error | DiagSeverity::Fatal => (self.error_open(), self.close()),
DiagSeverity::Warning => (self.warning_open(), self.close()),
DiagSeverity::Note | DiagSeverity::Remark => (self.note_open(), self.close()),
DiagSeverity::Ignored => (String::new(), String::new()),
};
format!("{}{}{}", open, text, close)
}
pub fn colorize_source_line(
&self,
source_line: &str,
caret_pos: usize,
range_len: usize,
) -> String {
if !self.use_colors {
if range_len <= 1 {
return format!(
" {}\n{:>caret_pos$}",
source_line,
"^",
caret_pos = caret_pos + 1
);
}
let tildes = "~".repeat(range_len.saturating_sub(1));
return format!(
" {}\n{:>caret_pos$}{}",
source_line,
"^",
tildes,
caret_pos = caret_pos + 1
);
}
let caret_open = Self::sequence(&self.caret_color);
let range_open = Self::sequence(&self.range_color);
let close = Self::sequence(&[TermColor::Reset]);
let source_open = Self::sequence(&self.source_color);
let source_close = Self::sequence(&[TermColor::Reset]);
format!(
" {}{}{}\n{}{:>caret_pos$}{}{}{}{}",
source_open,
source_line,
source_close,
caret_open,
"",
"^",
range_open,
if range_len > 1 {
"~".repeat(range_len.saturating_sub(1))
} else {
String::new()
},
close,
caret_pos = caret_pos + 1
)
}
pub fn caret_range_string(&self, start_col: usize, end_col: usize) -> String {
let width = end_col.saturating_sub(start_col);
if width == 0 {
return "^".to_string();
}
if width == 1 {
return "^".to_string();
}
let tildes_left = (width - 1) / 2;
let tildes_right = width.saturating_sub(tildes_left).saturating_sub(1);
format!(
"{}^\x08~{}",
"~".repeat(tildes_left),
"~".repeat(tildes_right)
)
}
}
impl Default for DiagColorScheme {
fn default() -> Self {
Self::clang_style()
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticPrinter {
pub color_scheme: DiagColorScheme,
pub show_column: bool,
pub show_source_line: bool,
pub show_carets: bool,
pub show_fixits: bool,
pub show_option_names: bool,
pub tab_stop: u32,
pub message_length: u32,
source_manager: ClangSourceManager,
}
impl DiagnosticPrinter {
pub fn new(sm: ClangSourceManager) -> Self {
Self {
color_scheme: DiagColorScheme::clang_style(),
show_column: true,
show_source_line: true,
show_carets: true,
show_fixits: true,
show_option_names: true,
tab_stop: 8,
message_length: 120,
source_manager: sm,
}
}
pub fn print(&self, diag: &StructuredDiagnostic) -> String {
let mut out = String::new();
let loc_str = if self.show_column && diag.location.is_valid {
format!(
"{}:{}:{}",
diag.location.filename(),
diag.location.line,
diag.location.column
)
} else if diag.location.is_valid {
format!("{}:{}", diag.location.filename(), diag.location.line)
} else {
String::from("<unknown>")
};
let sev_str = self
.color_scheme
.colorize(diag.severity, diag.severity.as_str());
let flag_str = if self.show_option_names {
if let Some(ref flag) = diag.flag_name {
if !flag.is_empty() {
format!(" [-W{}]", flag)
} else {
String::new()
}
} else {
String::new()
}
} else {
String::new()
};
out.push_str(&format!(
"{}: {}: {}{}\n",
loc_str, sev_str, diag.message, flag_str
));
if self.show_source_line && diag.location.is_valid {
if let Some(source_line) = self
.source_manager
.get_line(&diag.location.file, diag.location.line as u32)
{
let caret_col = diag.location.column as usize;
let range_len = if !diag.ranges.is_empty() {
let r = &diag.ranges[0];
if r.is_single_line() {
(r.end.column.saturating_sub(r.begin.column)) as usize
} else {
1
}
} else {
1
};
let colored_line = self.color_scheme.colorize_source_line(
&source_line,
caret_col.saturating_sub(1),
range_len.max(1),
);
out.push_str(&colored_line);
out.push('\n');
}
}
for note in &diag.notes {
let note_loc = if note.location.is_valid {
format!(
"{}:{}:{}",
note.location.filename(),
note.location.line,
note.location.column
)
} else {
String::from("<unknown>")
};
out.push_str(&format!("{}: note: {}\n", note_loc, note.message));
}
if self.show_fixits {
for fixit in &diag.fixits {
if fixit.is_insertion {
out.push_str(&format!(
" FIX-IT: insert \"{}\" at {}:{}\n",
fixit.code, fixit.remove_range.begin.line, fixit.remove_range.begin.column
));
} else if fixit.code.is_empty() {
out.push_str(&format!(
" FIX-IT: remove {}:{}-{}:{}\n",
fixit.remove_range.begin.line,
fixit.remove_range.begin.column,
fixit.remove_range.end.line,
fixit.remove_range.end.column
));
} else {
out.push_str(&format!(
" FIX-IT: replace with \"{}\" at {}:{}->{}:{}\n",
fixit.code,
fixit.remove_range.begin.line,
fixit.remove_range.begin.column,
fixit.remove_range.end.line,
fixit.remove_range.end.column
));
}
}
}
out
}
pub fn print_colored(&self, diag: &StructuredDiagnostic) -> String {
self.print(diag)
}
pub fn set_source_manager(&mut self, sm: ClangSourceManager) {
self.source_manager = sm;
}
}
impl Default for DiagnosticPrinter {
fn default() -> Self {
Self::new(ClangSourceManager::default())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticOutputStyle {
PlainText,
Markdown,
Html,
Sarif,
}
impl DiagnosticOutputStyle {
pub fn from_format(fmt: DiagnosticOutputFormat) -> Self {
match fmt {
DiagnosticOutputFormat::Clang
| DiagnosticOutputFormat::GCC
| DiagnosticOutputFormat::Vi => Self::PlainText,
DiagnosticOutputFormat::MSVC => Self::PlainText,
DiagnosticOutputFormat::JSON => Self::Sarif,
DiagnosticOutputFormat::SARIF => Self::Sarif,
DiagnosticOutputFormat::Serialized => Self::PlainText,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct WarningOptions {
pub pedantic: bool,
pub wall: bool,
pub wextra: bool,
pub weverything: bool,
pub werror: bool,
pub wno_all: Vec<String>,
pub wspecific: Vec<(String, DiagSeverity)>,
pub ignore_warnings: bool,
pub warnings_as_errors: bool,
}
impl WarningOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_wall(mut self) -> Self {
self.wall = true;
self
}
pub fn with_wextra(mut self) -> Self {
self.wextra = true;
self
}
pub fn with_weverything(mut self) -> Self {
self.weverything = true;
self
}
pub fn with_werror(mut self) -> Self {
self.warnings_as_errors = true;
self.werror = true;
self
}
pub fn ignore(&mut self, flag: &str) {
self.wno_all.push(flag.to_string());
}
pub fn error_for(&mut self, flag: &str) {
self.wspecific.push((flag.to_string(), DiagSeverity::Error));
}
pub fn ignore_warning(&mut self, flag: &str) {
self.wspecific
.push((flag.to_string(), DiagSeverity::Ignored));
}
pub fn apply_to(&self, engine: &mut DiagnosticEngine) {
if self.ignore_warnings {
engine.set_ignore_warnings(true);
return;
}
if self.warnings_as_errors {
engine.set_warnings_as_errors(true);
}
if self.wall {
engine.enable_all_warnings();
}
if self.pedantic || self.wextra {
engine.enable_pedantic_warnings();
}
for (flag, severity) in &self.wspecific {
engine.set_diagnostic_mapping(flag, *severity);
}
for flag in &self.wno_all {
engine.set_diagnostic_mapping(flag, DiagSeverity::Ignored);
}
}
}
#[derive(Debug, Clone)]
pub struct ErrorRecoveryHints {
pub expected_token_kind: Option<String>,
pub suggested_fix: Option<String>,
pub missing_include: Option<String>,
pub suggested_type: Option<String>,
pub suggested_declaration: Option<String>,
pub alternative_spelling: Option<String>,
}
impl ErrorRecoveryHints {
pub fn new() -> Self {
Self {
expected_token_kind: None,
suggested_fix: None,
missing_include: None,
suggested_type: None,
suggested_declaration: None,
alternative_spelling: None,
}
}
pub fn expected(mut self, kind: &str) -> Self {
self.expected_token_kind = Some(kind.to_string());
self
}
pub fn suggest_fix(mut self, fix: &str) -> Self {
self.suggested_fix = Some(fix.to_string());
self
}
pub fn missing_header(mut self, header: &str) -> Self {
self.missing_include = Some(header.to_string());
self
}
pub fn suggest_type(mut self, ty: &str) -> Self {
self.suggested_type = Some(ty.to_string());
self
}
pub fn alternative_spelling(mut self, alt: &str) -> Self {
self.alternative_spelling = Some(alt.to_string());
self
}
pub fn has_hints(&self) -> bool {
self.expected_token_kind.is_some()
|| self.suggested_fix.is_some()
|| self.missing_include.is_some()
|| self.suggested_type.is_some()
|| self.alternative_spelling.is_some()
}
pub fn format_hints(&self) -> String {
let mut hints = String::new();
if let Some(ref expected) = self.expected_token_kind {
hints.push_str(&format!(" expected: {}\n", expected));
}
if let Some(ref fix) = self.suggested_fix {
hints.push_str(&format!(" suggested fix: {}\n", fix));
}
if let Some(ref header) = self.missing_include {
hints.push_str(&format!(" did you forget to #include <{}>?\n", header));
}
if let Some(ref ty) = self.suggested_type {
hints.push_str(&format!(" suggested type: {}\n", ty));
}
if let Some(ref alt) = self.alternative_spelling {
hints.push_str(&format!(" did you mean '{}'?\n", alt));
}
hints
}
}
impl Default for ErrorRecoveryHints {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticSuppressionManager {
stack: Vec<Vec<(DiagID, DiagSeverity)>>,
per_file_mappings: std::collections::HashMap<String, Vec<(DiagID, DiagSeverity)>>,
globally_ignored: std::collections::HashSet<DiagID>,
}
impl DiagnosticSuppressionManager {
pub fn new() -> Self {
Self {
stack: vec![Vec::new()],
per_file_mappings: std::collections::HashMap::new(),
globally_ignored: std::collections::HashSet::new(),
}
}
pub fn push(&mut self) {
let top = self.stack.last().cloned().unwrap_or_default();
self.stack.push(top);
}
pub fn pop(&mut self) {
if self.stack.len() > 1 {
self.stack.pop();
}
}
pub fn set_mapping(&mut self, id: DiagID, severity: DiagSeverity) {
if let Some(current) = self.stack.last_mut() {
current.retain(|(existing_id, _)| *existing_id != id);
current.push((id, severity));
}
}
pub fn get_effective_severity(&self, id: DiagID, default: DiagSeverity) -> DiagSeverity {
if self.globally_ignored.contains(&id) {
return DiagSeverity::Ignored;
}
for frame in self.stack.iter().rev() {
for (mapped_id, severity) in frame.iter().rev() {
if *mapped_id == id {
return *severity;
}
}
}
default
}
pub fn ignore_globally(&mut self, id: DiagID) {
self.globally_ignored.insert(id);
}
pub fn reset(&mut self) {
self.stack.clear();
self.stack.push(Vec::new());
self.globally_ignored.clear();
}
}
impl Default for DiagnosticSuppressionManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diagid_default_severity() {
assert_eq!(
DiagID::WUnusedVariable.default_severity(),
DiagSeverity::Warning
);
assert_eq!(
DiagID::ErrExpectedSemiAfterExpr.default_severity(),
DiagSeverity::Error
);
assert_eq!(
DiagID::NotePreviousDefinition.default_severity(),
DiagSeverity::Note
);
assert_eq!(
DiagID::FatalNoInputFiles.default_severity(),
DiagSeverity::Fatal
);
assert_eq!(
DiagID::RemarkLoopVectorized.default_severity(),
DiagSeverity::Remark
);
assert_eq!(
DiagID::IgnoredMacroDefined.default_severity(),
DiagSeverity::Ignored
);
}
#[test]
fn test_diagid_flag_name() {
assert_eq!(
DiagID::WUnusedVariable.flag_name(),
Some("-Wunused-variable")
);
assert_eq!(DiagID::WSignCompare.flag_name(), Some("-Wsign-compare"));
assert_eq!(DiagID::WConversion.flag_name(), Some("-Wconversion"));
assert_eq!(DiagID::WShadow.flag_name(), Some("-Wshadow"));
assert_eq!(DiagID::WFormat.flag_name(), Some("-Wformat"));
assert_eq!(DiagID::WSwitch.flag_name(), Some("-Wswitch"));
assert_eq!(DiagID::WReturnType.flag_name(), Some("-Wreturn-type"));
assert_eq!(DiagID::WUninitialized.flag_name(), Some("-Wuninitialized"));
assert_eq!(DiagID::WAll.flag_name(), Some("-Wall"));
assert_eq!(DiagID::WPedantic.flag_name(), Some("-Wpedantic"));
assert_eq!(DiagID::WComment.flag_name(), Some("-Wcomment"));
assert_eq!(DiagID::WEmptyBody.flag_name(), Some("-Wempty-body"));
}
#[test]
fn test_diagid_groups() {
let groups = DiagID::WUnusedVariable.groups();
assert!(groups.contains(&"-Wunused"));
let groups = DiagID::WParentheses.groups();
assert!(groups.contains(&"-Wparentheses"));
assert!(groups.contains(&"-Wall"));
}
#[test]
fn test_diag_state_get_set() {
let mut state = DiagState::new();
assert_eq!(
state.get_severity(DiagID::WUnusedVariable),
DiagSeverity::Warning
);
state.set_severity(DiagID::WUnusedVariable, DiagSeverity::Error);
assert_eq!(
state.get_severity(DiagID::WUnusedVariable),
DiagSeverity::Error
);
state.set_severity(DiagID::WUnusedVariable, DiagSeverity::Ignored);
assert_eq!(
state.get_severity(DiagID::WUnusedVariable),
DiagSeverity::Ignored
);
}
#[test]
fn test_diag_state_group() {
let mut state = DiagState::new();
state.set_group("Wunused", DiagSeverity::Error);
assert_eq!(
state.get_severity(DiagID::WUnusedVariable),
DiagSeverity::Error
);
}
#[test]
fn test_source_location() {
let loc = ClangSourceLocation::new(Path::new("test.c"), 10, 5);
assert!(loc.is_valid);
assert_eq!(loc.line, 10);
assert_eq!(loc.column, 5);
assert_eq!(loc.filename(), "test.c");
let invalid = ClangSourceLocation::invalid();
assert!(!invalid.is_valid);
}
#[test]
fn test_source_range() {
let begin = ClangSourceLocation::new(Path::new("test.c"), 1, 1);
let end = ClangSourceLocation::new(Path::new("test.c"), 1, 10);
let range = SourceRange::new(begin, end);
assert!(range.is_valid());
assert!(range.is_single_line());
let begin2 = ClangSourceLocation::new(Path::new("test.c"), 1, 1);
let end2 = ClangSourceLocation::new(Path::new("test.c"), 2, 1);
let range2 = SourceRange::new(begin2, end2);
assert!(range2.is_multiline());
}
#[test]
fn test_fixit_hint() {
let loc = ClangSourceLocation::new(Path::new("test.c"), 1, 5);
let insert = FixItHint::insertion(loc.clone(), "const ");
assert!(insert.is_insertion);
assert_eq!(insert.code, "const ");
let range = SourceRange::point(loc);
let remove = FixItHint::removal(range.clone());
assert!(!remove.is_insertion);
assert!(remove.code.is_empty());
let replace = FixItHint::replacement(range, "int");
assert!(!replace.is_insertion);
assert_eq!(replace.code, "int");
}
#[test]
fn test_structured_diagnostic() {
let loc = ClangSourceLocation::new(Path::new("test.c"), 42, 7);
let diag = StructuredDiagnostic::new(
DiagID::ErrUndeclaredIdentifier,
DiagSeverity::Error,
"use of undeclared identifier 'x'".to_string(),
loc,
);
assert_eq!(diag.severity, DiagSeverity::Error);
assert!(diag.flag_name.is_none());
}
#[test]
fn test_diagnostic_engine_basic() {
let sm = ClangSourceManager::new();
let mut engine = DiagnosticEngine::new(sm.clone());
assert!(!engine.has_errors());
assert!(!engine.has_warnings());
assert_eq!(engine.error_count, 0);
}
#[test]
fn test_diagnostic_renderer_caret() {
let options = DiagnosticOptions::default();
let renderer = DiagnosticRenderer::new(options);
let line = "int x = 42;";
let caret = renderer.render_caret(line, 5, &[]);
assert!(caret.contains('^'));
}
#[test]
fn test_warning_group_registry() {
let registry = WarningGroupRegistry::new();
assert!(registry.has_group("Wall"));
assert!(registry.has_group("Wextra"));
assert!(registry.has_group("Wpedantic"));
assert!(registry.has_group("Wunused"));
let resolved = registry.resolve_group("Wall");
assert!(!resolved.is_empty());
assert!(resolved.contains(&"Wunused-variable".to_string()));
}
#[test]
fn test_diag_severity_from_str() {
assert_eq!(
DiagSeverity::from_str("warning"),
Some(DiagSeverity::Warning)
);
assert_eq!(DiagSeverity::from_str("error"), Some(DiagSeverity::Error));
assert_eq!(
DiagSeverity::from_str("ignored"),
Some(DiagSeverity::Ignored)
);
assert_eq!(DiagSeverity::from_str("fatal"), Some(DiagSeverity::Fatal));
assert_eq!(DiagSeverity::from_str("unknown"), None);
}
#[test]
fn test_error_message_catalog() {
let msg = ErrorMessageCatalog::cannot_convert("test.c", 10, 5, "int *", "char *");
assert!(msg.contains("error"));
assert!(msg.contains("cannot convert"));
assert!(msg.contains("int *"));
assert!(msg.contains("char *"));
}
#[test]
fn test_diagnostic_options_defaults() {
let opts = DiagnosticOptions::default();
assert!(!opts.warnings_as_errors);
assert!(opts.show_carets);
assert!(opts.show_location);
assert_eq!(opts.error_limit, 20);
assert_eq!(opts.tab_stop, 8);
}
#[test]
fn test_source_manager_add_and_get() {
let mut sm = ClangSourceManager::new();
sm.add_file(Path::new("test.c"), "int main() {\n return 0;\n}\n");
assert!(sm.has_file(Path::new("test.c")));
assert_eq!(sm.file_count(), 1);
let line = sm.get_line(Path::new("test.c"), 2);
assert_eq!(line, Some(" return 0;".to_string()));
}
#[test]
fn test_source_file_line_offsets() {
let sf = ClangSourceFile::new(Path::new("test.c"), "line1\nline2\nline3\n");
assert_eq!(sf.line_offsets.len(), 4); assert_eq!(sf.get_line(1), Some("line1".to_string()));
assert_eq!(sf.get_line(2), Some("line2".to_string()));
assert_eq!(sf.get_line(3), Some("line3".to_string()));
let sf2 = ClangSourceFile::new(Path::new("empty.c"), "");
assert_eq!(sf2.line_offsets.len(), 1);
assert_eq!(sf2.get_line(1), Some("".to_string()));
}
#[test]
fn test_diagnostic_builder() {
let sm = ClangSourceManager::new();
let mut engine = DiagnosticEngine::new(sm);
let loc = ClangSourceLocation::new(Path::new("test.c"), 1, 5);
let mut builder = engine.report(DiagID::ErrExpectedSemiAfterExpr);
builder.at(loc).emit();
assert!(engine.has_errors());
assert_eq!(engine.error_count, 1);
}
}