[Git][ghc/ghc][wip/sjakobi/multi-caret] 4 commits: Include related source spans in diagnostic JSON output
Simon Jakobi pushed to branch wip/sjakobi/multi-caret at Glasgow Haskell Compiler / GHC Commits: 5a375868 by Simon Jakobi at 2026-06-03T19:45:38+02:00 Include related source spans in diagnostic JSON output The diagnostics-as-json output was discarding the related source spans carried by a Diagnostic. Emit them as a new "relatedSpans" field and bump the JSON schema version 1.2 -> 1.3 (additive, backwards compatible). The ToJson MessageClass instance no longer drops the spans either. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> - - - - - d380c9c5 by Simon Jakobi at 2026-06-03T19:46:10+02:00 Replace wildcard in diagnosticRelatedLocations with explicit cases Listing every TcRnMessage constructor explicitly (rather than a catch-all wildcard) means adding a new constructor now triggers an incomplete-patterns warning here, prompting consideration of whether it has related locations. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> - - - - - 1c349496 by Simon Jakobi at 2026-06-03T19:46:16+02:00 Document the source span model for diagnostics Add Note [The source span model for diagnostics] describing the single primary span ('errMsgSpan') plus related spans ('diagnosticRelatedLocations') model and its correspondence to the LSP Diagnostic / relatedInformation interfaces, and point the diagnosticRelatedLocations haddock at it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> - - - - - bc6a7307 by Simon Jakobi at 2026-06-03T19:46:22+02:00 Dedup caret source spans against the primary span Wrap the primary :| related span construction in nub at both render sites so a related location that coincides with the primary is not caretted twice, as described in Note [The source span model for diagnostics]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> - - - - - 11 changed files: - compiler/GHC/Driver/Errors.hs - compiler/GHC/Tc/Errors/Ppr.hs - compiler/GHC/Types/Error.hs - compiler/GHC/Utils/Logger.hs - + docs/users_guide/diagnostics-as-json-schema-1_3.json - docs/users_guide/using.rst - testsuite/tests/driver/T16167.stdout - testsuite/tests/driver/json.stderr - testsuite/tests/driver/json2.stderr - testsuite/tests/driver/json_dump.stderr - testsuite/tests/driver/json_warn.stderr Changes: ===================================== compiler/GHC/Driver/Errors.hs ===================================== @@ -15,7 +15,7 @@ import GHC.Utils.Json import GHC.Utils.Error import GHC.Utils.Outputable import GHC.Utils.Logger -import Data.List.NonEmpty (NonEmpty(..)) +import Data.List.NonEmpty (NonEmpty(..), nub) reportError :: Logger -> NamePprCtx -> DiagOpts -> SrcSpan -> SDoc -> IO () reportError logger nameContext opts span doc = do @@ -85,7 +85,7 @@ printMessage logger msg_opts opts message relatedSpans = diagnosticRelatedLocations diagnostic sourceSpans :: NonEmpty SrcSpan - sourceSpans = location :| relatedSpans + sourceSpans = nub (location :| relatedSpans) severity :: Severity severity = errMsgSeverity message ===================================== compiler/GHC/Tc/Errors/Ppr.hs ===================================== @@ -3470,8 +3470,305 @@ instance Diagnostic TcRnMessage where -> [getLocA lie2] TcRnDuplicateNamedDefaultExport _ _lie1 lie2 -> [getLocA lie2] - _ -> - [] + -- The remaining constructors have no related locations (yet). They are listed + -- explicitly (rather than matched with a wildcard) so that adding a new + -- constructor produces an incomplete-patterns warning here, prompting + -- whoever adds it to consider whether it has related locations. + TcRnInterfaceError{} -> [] + TcRnSolverReport{} -> [] + TcRnSolverDepthError{} -> [] + TcRnRedundantConstraints{} -> [] + TcRnInaccessibleCode{} -> [] + TcRnInaccessibleCoAxBranch{} -> [] + TcRnTypeDoesNotHaveFixedRuntimeRep{} -> [] + TcRnImplicitLift{} -> [] + TcRnUnusedPatternBinds{} -> [] + TcRnUnusedQuantifiedTypeVar{} -> [] + TcRnDodgyImports{} -> [] + TcRnDodgyExports{} -> [] + TcRnMissingImportList{} -> [] + TcRnUnsafeDueToPlugin{} -> [] + TcRnModMissingRealSrcSpan{} -> [] + TcRnIdNotExportedFromModuleSig{} -> [] + TcRnIdNotExportedFromLocalSig{} -> [] + TcRnShadowedName{} -> [] + TcRnInvalidWarningCategory{} -> [] + TcRnDuplicateWarningDecls{} -> [] + TcRnSimplifierTooManyIterations{} -> [] + TcRnIllegalPatSynDecl{} -> [] + TcRnLinearPatSyn{} -> [] + TcRnEmptyRecordUpdate{} -> [] + TcRnIllegalFieldPunning{} -> [] + TcRnIllegalWildcardsInRecord{} -> [] + TcRnIllegalWildcardInType{} -> [] + TcRnIllegalNamedWildcardInTypeArgument{} -> [] + TcRnIllegalImplicitTyVarInTypeArgument{} -> [] + TcRnIllegalPunnedVarOccInTypeArgument{} -> [] + TcRnDuplicateFieldName{} -> [] + TcRnIllegalViewPattern{} -> [] + TcRnCharLiteralOutOfRange{} -> [] + TcRnNegativeNumTypeLiteral{} -> [] + TcRnIllegalWildcardsInConstructor{} -> [] + TcRnIgnoringAnnotations{} -> [] + TcRnAnnotationInSafeHaskell{} -> [] + TcRnInvalidTypeApplication{} -> [] + TcRnTagToEnumMissingValArg{} -> [] + TcRnTagToEnumUnspecifiedResTy{} -> [] + TcRnTagToEnumResTyNotAnEnum{} -> [] + TcRnTagToEnumResTyTypeData{} -> [] + TcRnArrowIfThenElsePredDependsOnResultTy{} -> [] + TcRnIllegalHsBootOrSigDecl{} -> [] + TcRnBootMismatch{} -> [] + TcRnRecursivePatternSynonym{} -> [] + TcRnPartialTypeSigTyVarMismatch{} -> [] + TcRnPartialTypeSigBadQuantifier{} -> [] + TcRnMissingSignature{} -> [] + TcRnPolymorphicBinderMissingSig{} -> [] + TcRnOverloadedSig{} -> [] + TcRnTupleConstraintInst{} -> [] + TcRnUserTypeError{} -> [] + TcRnConstraintInKind{} -> [] + TcRnUnboxedTupleOrSumTypeFuncArg{} -> [] + TcRnLinearFuncInKind{} -> [] + TcRnForAllEscapeError{} -> [] + TcRnVDQInTermType{} -> [] + TcRnBadQuantPredHead{} -> [] + TcRnIllegalTupleConstraint{} -> [] + TcRnNonTypeVarArgInConstraint{} -> [] + TcRnIllegalImplicitParam{} -> [] + TcRnIllegalConstraintSynonymOfKind{} -> [] + TcRnOversaturatedVisibleKindArg{} -> [] + TcRnForAllRankErr{} -> [] + TcRnSimplifiableConstraint{} -> [] + TcRnArityMismatch{} -> [] + TcRnIllegalInstance{} -> [] + TcRnMonomorphicBindings{} -> [] + TcRnOrphanInstance{} -> [] + TcRnFunDepConflict{} -> [] + TcRnDupInstanceDecls{} -> [] + TcRnConflictingFamInstDecls{} -> [] + TcRnFamInstNotInjective{} -> [] + TcRnBangOnUnliftedType{} -> [] + TcRnLazyBangOnUnliftedType{} -> [] + TcRnMultipleDefaultDeclarations{} -> [] + TcRnWarnClashingDefaultImports{} -> [] + TcRnBadDefaultType{} -> [] + TcRnPatSynBundledWithNonDataCon{} -> [] + TcRnPatSynBundledWithWrongType{} -> [] + TcRnDupeModuleExport{} -> [] + TcRnDupeWildcardExport{} -> [] + TcRnExportedModNotImported{} -> [] + TcRnMissingExportList{} -> [] + TcRnExportHiddenDefault{} -> [] + TcRnExportedParentChildMismatch{} -> [] + TcRnExportedSubordinateNotFound{} -> [] + TcRnConflictingExports{} -> [] + TcRnDuplicateFieldExport{} -> [] + TcRnAmbiguousRecordUpdate{} -> [] + TcRnMissingFields{} -> [] + TcRnFieldUpdateInvalidType{} -> [] + TcRnMissingStrictFields{} -> [] + TcRnAmbiguousFieldInUpdate{} -> [] + TcRnBadRecordUpdate{} -> [] + TcRnStaticFormNotClosed{} -> [] + TcRnUselessTypeable{} -> [] + TcRnDerivingDefaults{} -> [] + TcRnNonUnaryTypeclassConstraint{} -> [] + TcRnPartialTypeSignatures{} -> [] + TcRnCannotDeriveInstance{} -> [] + TcRnLazyGADTPattern{} -> [] + TcRnArrowProcGADTPattern{} -> [] + TcRnCapturedTermName{} -> [] + TcRnTypeEqualityOutOfScope{} -> [] + TcRnTypeEqualityRequiresOperators{} -> [] + TcRnIllegalTypeOperator{} -> [] + TcRnIllegalTypeOperatorDecl{} -> [] + TcRnGADTMonoLocalBinds{} -> [] + TcRnNotInScope{} -> [] + TcRnTermNameInType{} -> [] + TcRnUntickedPromotedThing{} -> [] + TcRnIllegalBuiltinSyntax{} -> [] + TcRnWarnDefaulting{} -> [] + TcRnIncorrectNameSpace{} -> [] + TcRnForeignImportPrimExtNotSet{} -> [] + TcRnForeignImportPrimSafeAnn{} -> [] + TcRnForeignFunctionImportAsValue{} -> [] + TcRnFunPtrImportWithoutAmpersand{} -> [] + TcRnIllegalForeignDeclBackend{} -> [] + TcRnUnsupportedCallConv{} -> [] + TcRnIllegalForeignType{} -> [] + TcRnInvalidCIdentifier{} -> [] + TcRnExpectedValueId{} -> [] + TcRnRecSelectorEscapedTyVar{} -> [] + TcRnPatSynNotBidirectional{} -> [] + TcRnIllegalDerivingItem{} -> [] + TcRnIllegalDefaultClass{} -> [] + TcRnIllegalNamedDefault{} -> [] + TcRnUnexpectedAnnotation{} -> [] + TcRnIllegalRecordSyntax{} -> [] + TcRnInvalidVisibleKindArgument{} -> [] + TcRnTooManyBinders{} -> [] + TcRnDifferentNamesForTyVar{} -> [] + TcRnDisconnectedTyVar{} -> [] + TcRnInvalidReturnKind{} -> [] + TcRnUnexpectedKindVar{} -> [] + TcRnIllegalKind{} -> [] + TcRnClassKindNotConstraint{} -> [] + TcRnUnpromotableThing{} -> [] + TcRnUnpromotableLit{} -> [] + TcRnIllegalTermLevelUse{} -> [] + TcRnMatchesHaveDiffNumArgs{} -> [] + TcRnUnexpectedPatSigType{} -> [] + TcRnDataKindsError{} -> [] + TcRnCannotBindScopedTyVarInPatSig{} -> [] + TcRnCannotBindTyVarsInPatBind{} -> [] + TcRnMultipleInlinePragmas{} -> [] + TcRnUnexpectedPragmas{} -> [] + TcRnNonOverloadedSpecialisePragma{} -> [] + TcRnSpecialiseNotVisible{} -> [] + TcRnPragmaWarning{} -> [] + TcRnDifferentExportWarnings{} -> [] + TcRnIncompleteExportWarnings{} -> [] + TcRnIllegalHsigDefaultMethods{} -> [] + TcRnHsigFixityMismatch{} -> [] + TcRnHsigShapeMismatch{} -> [] + TcRnHsigMissingModuleExport{} -> [] + TcRnBadGenericMethod{} -> [] + TcRnWarningMinimalDefIncomplete{} -> [] + TcRnIllegalQuasiQuotes{} -> [] + TcRnTHError{} -> [] + TcRnDefaultMethodForPragmaLacksBinding{} -> [] + TcRnIgnoreSpecialisePragmaOnDefMethod{} -> [] + TcRnBadMethodErr{} -> [] + TcRnIllegalNewtype{} -> [] + TcRnIllegalTypeData{} -> [] + TcRnTypeDataForbids{} -> [] + TcRnOrPatBindsVariables{} -> [] + TcRnUnsatisfiedMinimalDef{} -> [] + TcRnMisplacedInstSig{} -> [] + TcRnNoRebindableSyntaxRecordDot{} -> [] + TcRnNoFieldPunsRecordDot{} -> [] + TcRnIllegalStaticExpression{} -> [] + TcRnListComprehensionDuplicateBinding{} -> [] + TcRnEmptyStmtsGroup{} -> [] + TcRnLastStmtNotExpr{} -> [] + TcRnUnexpectedStatementInContext{} -> [] + TcRnIllegalTupleSection{} -> [] + TcRnIllegalImplicitParameterBindings{} -> [] + TcRnSectionWithoutParentheses{} -> [] + TcRnBindingOfExistingName{} -> [] + TcRnMultipleFixityDecls{} -> [] + TcRnIllegalPatternSynonymDecl{} -> [] + TcRnIllegalClassBinding{} -> [] + TcRnOrphanCompletePragma{} -> [] + TcRnEmptyCase{} -> [] + TcRnNonStdGuards{} -> [] + TcRnDuplicateSigDecl{} -> [] + TcRnMisplacedSigDecl{} -> [] + TcRnUnexpectedDefaultSig{} -> [] + TcRnDuplicateMinimalSig{} -> [] + TcRnSpecSigShape{} -> [] + TcRnIllegalInvisTyVarBndr{} -> [] + TcRnIllegalWildcardTyVarBndr{} -> [] + TcRnInvalidInvisTyVarBndr{} -> [] + TcRnInvisBndrWithoutSig{} -> [] + TcRnUnexpectedStandaloneDerivingDecl{} -> [] + TcRnUnusedVariableInRuleDecl{} -> [] + TcRnUnexpectedStandaloneKindSig{} -> [] + TcRnIllegalRuleLhs{} -> [] + TcRnRuleLhsEqualities{} -> [] + TcRnDuplicateRoleAnnot{} -> [] + TcRnDuplicateKindSig{} -> [] + TcRnIllegalDerivStrategy{} -> [] + TcRnIllegalMultipleDerivClauses{} -> [] + TcRnNoDerivStratSpecified{} -> [] + TcRnStupidThetaInGadt{} -> [] + TcRnShadowedTyVarNameInFamResult{} -> [] + TcRnIncorrectTyVarOnLhsOfInjCond{} -> [] + TcRnUnknownTyVarsOnRhsOfInjCond{} -> [] + TcRnLookupInstance{} -> [] + TcRnBadlyLevelled{} -> [] + TcRnBadlyLevelledType{} -> [] + TcRnTyThingUsedWrong{} -> [] + TcRnCannotDefaultKindVar{} -> [] + TcRnUninferrableTyVar{} -> [] + TcRnSkolemEscape{} -> [] + TcRnPatSynEscapedCoercion{} -> [] + TcRnPatSynExistentialInResult{} -> [] + TcRnPatSynArityMismatch{} -> [] + TcRnPatSynInvalidRhs{} -> [] + TcRnZonkerMessage{} -> [] + TcRnTyFamDepsDisabled{} -> [] + TcRnAbstractClosedTyFamDecl{} -> [] + TcRnPartialFieldSelector{} -> [] + TcRnHasFieldResolvedIncomplete{} -> [] + TcRnBadFieldAnnotation{} -> [] + TcRnSuperclassCycle{} -> [] + TcRnDefaultSigMismatch{} -> [] + TcRnTyFamsDisabled{} -> [] + TcRnBadTyConTelescope{} -> [] + TcRnTyFamResultDisabled{} -> [] + TcRnRoleValidationFailed{} -> [] + TcRnCommonFieldResultTypeMismatch{} -> [] + TcRnCommonFieldTypeMismatch{} -> [] + TcRnClassExtensionDisabled{} -> [] + TcRnDataConParentTypeMismatch{} -> [] + TcRnGADTsDisabled{} -> [] + TcRnExistentialQuantificationDisabled{} -> [] + TcRnGADTDataContext{} -> [] + TcRnMultipleConForNewtype{} -> [] + TcRnKindSignaturesDisabled{} -> [] + TcRnEmptyDataDeclsDisabled{} -> [] + TcRnRoleMismatch{} -> [] + TcRnRoleCountMismatch{} -> [] + TcRnIllegalRoleAnnotation{} -> [] + TcRnRoleAnnotationsDisabled{} -> [] + TcRnIncoherentRoles{} -> [] + TcRnPrecedenceParsingError{} -> [] + TcRnSectionPrecedenceError{} -> [] + TcRnTypeSynonymCycle{} -> [] + TcRnSelfImport{} -> [] + TcRnNoExplicitImportList{} -> [] + TcRnSafeImportsDisabled{} -> [] + TcRnDeprecatedModule{} -> [] + TcRnRedundantSourceImport{} -> [] + TcRnImportLookup{} -> [] + TcRnUnusedImport{} -> [] + TcRnPackageImportsDisabled{} -> [] + TcRnIllegalDataCon{} -> [] + TcRnNestedForallsContexts{} -> [] + TcRnRedundantRecordWildcard{} -> [] + TcRnUnusedRecordWildcard{} -> [] + TcRnUnusedName{} -> [] + TcRnQualifiedBinder{} -> [] + TcRnTypeApplicationsDisabled{} -> [] + TcRnInvalidRecordField{} -> [] + TcRnTupleTooLarge{} -> [] + TcRnCTupleTooLarge{} -> [] + TcRnIllegalInferredTyVars{} -> [] + TcRnAmbiguousName{} -> [] + TcRnNonCanonicalDefinition{} -> [] + TcRnImplicitImportOfPrelude{} -> [] + TcRnMissingMain{} -> [] + TcRnGhciUnliftedBind{} -> [] + TcRnGhciMonadLookupFail{} -> [] + TcRnMissingRoleAnnotation{} -> [] + TcRnPatersonCondFailure{} -> [] + TcRnImplicitRhsQuantification{} -> [] + TcRnIllformedTypePattern{} -> [] + TcRnIllegalTypePattern{} -> [] + TcRnIllformedTypeArgument{} -> [] + TcRnIllegalTypeExpr{} -> [] + TcRnInvalidDefaultedTyVar{} -> [] + TcRnNamespacedWarningPragmaWithoutFlag{} -> [] + TcRnIllegalInvisibleTypePattern{} -> [] + TcRnNamespacedFixitySigWithoutFlag{} -> [] + TcRnDefaultedExceptionContext{} -> [] + TcRnOutOfArityTyVar{} -> [] + TcRnUnexpectedTypeSyntaxInTerms{} -> [] + TcRnUnrecognisedModifier{} -> [] + TcRnUnknownModifierKind{} -> [] + TcRnTooManyMultiplicities{} -> [] pprTcRnBadlyLevelled :: LevelCheckReason -> Set.Set ThLevelIndex -> ThLevelIndex -> Maybe ErrorItem -> DecoratedSDoc pprTcRnBadlyLevelled reason bind_lvls use_lvl lift_attempt = mkDecorated $ ===================================== compiler/GHC/Types/Error.hs ===================================== @@ -234,6 +234,51 @@ defaultDiagnosticOpts :: forall opts . HasDefaultDiagnosticOpts (DiagnosticOpts defaultDiagnosticOpts = defaultOpts @(DiagnosticOpts opts) +{- Note [The source span model for diagnostics] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Every diagnostic is associated with source locations on two levels: + + * One /primary/ span: the 'errMsgSpan' of the 'MsgEnvelope'. This is the + single location that identifies "where" the diagnostic is reported. It + drives the @file:line:col:@ message prefix, the order in which messages are + sorted, and the squiggle an editor draws for the diagnostic. The primary + span is always rendered with a caret ('^'). + + * Zero or more /related/ spans: 'diagnosticRelatedLocations'. These are + /additional/ locations that help explain the diagnostic — for a + duplicate-binding error, say, the other binding sites. They are rendered as + further carets alongside the primary span, and are exposed to tooling (e.g. + via -fdiagnostics-as-json, and onwards to HLS/LSP). + +This is close to the diagnostic model of the Language Server Protocol (LSP), to +which GHC's diagnostics are forwarded by HLS. An LSP diagnostic carries a single +@range@ plus a list of @relatedInformation@ entries, each a location with its +own message; GHC's 'errMsgSpan' corresponds to the @range@ and +'diagnosticRelatedLocations' to @relatedInformation@. See the @Diagnostic@ and +@DiagnosticRelatedInformation@ interfaces under "Basic JSON Structures": + + https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17... + +A couple of points follow from this: + + * Related spans are additional to the primary span, not replacements for it. + The spans that receive a caret are @primary :| relatedLocations@ (see + 'GHC.Driver.Errors.printMessage' and 'defaultLogActionWithHandles'), with + duplicates removed so that a related span coinciding with the primary is not + caretted twice. + + * The primary span need not be the leftmost or smallest of the locations + involved, and need not be a synthetic span combining all of them. When + several real locations are in play, it is usually better to pick one as the + primary and list the rest as related locations than to report a single + combined span, which gives a less precise prefix and a larger caret. + +Note that GHC's related locations are currently bare 'SrcSpan's with no per-span +label, whereas an LSP @relatedInformation@ entry pairs each location with a +message (as do rustc's span labels). Carrying such a message per related span is +a natural future extension; see #23414. +-} + -- | A class identifying a diagnostic. -- Dictionary.com defines a diagnostic as: -- @@ -281,7 +326,7 @@ class (Outputable (DiagnosticHint a), HasDefaultDiagnosticOpts (DiagnosticOpts a -- | Additional locations related to this diagnostic. -- -- When rendering caret diagnostics, these locations are shown alongside the - -- message's primary location. + -- message's primary location. See Note [The source span model for diagnostics]. diagnosticRelatedLocations :: a -> [SrcSpan] diagnosticRelatedLocations _ = [] @@ -567,8 +612,8 @@ instance ToJson MessageClass where json MCInteractive = JSString "MCInteractive" json MCDump = JSString "MCDump" json MCInfo = JSString "MCInfo" - json (MCDiagnostic sev reason code _) = - JSString $ renderWithContext defaultSDocContext (ppr $ text "MCDiagnostic" <+> ppr sev <+> ppr reason <+> ppr code) + json (MCDiagnostic sev reason code relatedSpans) = + JSString $ renderWithContext defaultSDocContext (ppr $ text "MCDiagnostic" <+> ppr sev <+> ppr reason <+> ppr code <+> ppr relatedSpans) instance ToJson DiagnosticCode where json c = JSInt (fromIntegral (diagnosticCodeNumber c)) @@ -576,7 +621,7 @@ instance ToJson DiagnosticCode where {- Note [Diagnostic Message JSON Schema] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The below instance of ToJson must conform to the JSON schema -specified in docs/users_guide/diagnostics-as-json-schema-1_2.json. +specified in docs/users_guide/diagnostics-as-json-schema-1_3.json. When the schema is altered, please bump the version. If the content is altered in a backwards compatible way, update the minor version (e.g. 1.3 ~> 1.4). @@ -589,7 +634,7 @@ https://json-schema.org -} schemaVersion :: String -schemaVersion = "1.2" +schemaVersion = "1.3" -- See Note [Diagnostic Message JSON Schema] before editing! jsonDiagnostic :: forall e. Diagnostic e => String -> MsgEnvelope e -> JsonDoc @@ -597,6 +642,7 @@ jsonDiagnostic rendered m = JSObject $ [ ("version", JSString schemaVersion), ("ghcVersion", JSString $ "ghc-" ++ cProjectVersion), ("span", json $ errMsgSpan m), + ("relatedSpans", JSArray $ map json (diagnosticRelatedLocations diag)), ("severity", json $ errMsgSeverity m), ("code", maybe JSNull json (diagnosticCode diag)), ("rendered", JSString rendered), ===================================== compiler/GHC/Utils/Logger.hs ===================================== @@ -102,7 +102,7 @@ import System.FilePath ( takeDirectory, (</>) ) import qualified Data.Map.Strict as Map import Data.Map.Strict (Map) import Data.List (stripPrefix) -import Data.List.NonEmpty (NonEmpty(..)) +import Data.List.NonEmpty (NonEmpty(..), nub) import Data.Time import System.IO import Control.Monad @@ -425,7 +425,7 @@ defaultLogActionWithHandles out err logflags msg_class srcSpan msg MCFatal -> printErrs msg MCDiagnostic SevIgnore _ _ _ -> pure () -- suppress the message MCDiagnostic _sev _rea _code relatedSpans -> - decorateDiagnostic logflags msg_class srcSpan (srcSpan :| relatedSpans) msg >>= printErrs + decorateDiagnostic logflags msg_class srcSpan (nub (srcSpan :| relatedSpans)) msg >>= printErrs where printOut = defaultLogActionHPrintDoc logflags False out printErrs = defaultLogActionHPrintDoc logflags False err ===================================== docs/users_guide/diagnostics-as-json-schema-1_3.json ===================================== @@ -0,0 +1,154 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "JSON Diagnostic Schema", + "description": "A Schema for specifying GHC diagnostics output as JSON", + "type": "object", + "properties": { + "version": { + "description": "The current JSON schema version this object conforms to", + "type": "string" + }, + "ghcVersion": { + "description": "The GHC version", + "type": "string" + }, + "span": { + "oneOf": [ + { "$ref": "#/$defs/span" }, + { "type": "null" } + ] + }, + "relatedSpans": { + "description": "Additional source spans related to the diagnostic, beyond the primary span", + "type": "array", + "items": { + "oneOf": [ + { "$ref": "#/$defs/span" }, + { "type": "null" } + ] + } + }, + "severity": { + "description": "The diagnostic severity", + "type": "string", + "enum": [ + "Warning", + "Error" + ] + }, + "code": { + "description": "The diagnostic code (if it exists)", + "type": [ + "integer", + "null" + ] + }, + "rendered": { + "description": "The rendered diagnostics message, in the exact same format as what GHC would have produced without -fdiagnostics-as-json (including ANSI escape sequences)", + "type": "string" + }, + "message": { + "description": "The string output of the diagnostic message by GHC", + "type": "array", + "items": { + "type": "string" + } + }, + "hints": { + "description": "The suggested fixes", + "type": "array", + "items": { + "type": "string" + } + }, + "reason" : { + "description": "The GHC flag that was responsible for the emission of the diagnostic message", + "oneOf": [ + { + "type": "object", + "description": "The diagnostic message was controlled by one or more GHC flags", + "properties": { + "flags": { + "type": "array", + "items": { + "description": "The name of a GHC flag controlling the diagnostic message", + "type": "string" + }, + "minItems": 1 + } + }, + "required": ["flags"] + }, + { + "type": "object", + "description": "The diagnostic message was controlled by a GHC diagnostic message category", + "properties": { + "category": { + "description": "The name of the GHC diagnostic message category controlling the diagnostic message", + "type": "string" + } + }, + "required": ["category"] + } + ] + } + }, + + "$comment": "NOTE: \"rendered\" and \"relatedSpans\" are not required fields so that the schema is backward compatible with versions 1.1 and 1.2 respectively. If you bump the schema version to 2.0 then please also add \"rendered\" to the \"required\" fields.", + "required": [ + "version", + "ghcVersion", + "span", + "severity", + "code", + "message", + "hints" + ], + + "additionalProperties": false, + "$defs": { + "span": { + "description": "The span of the diagnostic", + "type": "object", + "properties": { + "file": { + "description": "The file in which the diagnostic occurs", + "type": "string" + }, + "start": { + "description": "The start location of the diagnostic", + "$ref": "#/$defs/location" + }, + "end": { + "description": "The end location of the diagnostic", + "$ref": "#/$defs/location" + } + }, + "required": [ + "file", + "start", + "end" + ], + "additionalProperties": false + }, + "location": { + "description": "A location in a text file", + "type": "object", + "properties": { + "line": { + "description": "The line number", + "type": "integer" + }, + "column": { + "description": "The column number", + "type": "integer" + } + }, + "required": [ + "line", + "column" + ], + "additionalProperties": false + } + } +} ===================================== docs/users_guide/using.rst ===================================== @@ -1437,7 +1437,7 @@ messages and in GHCi: a new line. The structure of the output is described by a `JSON Schema <https://json-schema.org/>`_. - The schema can be downloaded :download:`here <diagnostics-as-json-schema-1_2.json>`. + The schema can be downloaded :download:`here <diagnostics-as-json-schema-1_3.json>`. .. ghc-flag:: -fdiagnostics-color=⟨always|auto|never⟩ :shortdesc: Use colors in error messages ===================================== testsuite/tests/driver/T16167.stdout ===================================== @@ -1,2 +1,2 @@ -{"span":null,"doc":"-ddump-json is deprecated: Use `-fdiagnostics-as-json` instead","messageClass":"MCDiagnostic SevWarning WarningWithFlags Opt_WarnDeprecatedFlags :| [] Just GHC-53692"} -{"span":{"file":"T16167.hs","startLine":1,"startCol":8,"endLine":1,"endCol":9},"doc":"parse error on input \u2018f\u2019","messageClass":"MCDiagnostic SevError ErrorWithoutFlag Just GHC-58481"} +{"span":null,"doc":"-ddump-json is deprecated: Use `-fdiagnostics-as-json` instead","messageClass":"MCDiagnostic SevWarning WarningWithFlags Opt_WarnDeprecatedFlags :| [] Just GHC-53692 []"} +{"span":{"file":"T16167.hs","startLine":1,"startCol":8,"endLine":1,"endCol":9},"doc":"parse error on input \u2018f\u2019","messageClass":"MCDiagnostic SevError ErrorWithoutFlag Just GHC-58481 []"} ===================================== testsuite/tests/driver/json.stderr ===================================== @@ -1 +1 @@ -{"version":"1.2","ghcVersion":"ghc-9.13.20250627","span":{"file":"json.hs","start":{"line":9,"column":11},"end":{"line":9,"column":21}},"severity":"Error","code":48010,"rendered":"json.hs:9:11: error: [GHC-48010]\n Empty list of alternatives in case expression\n Suggested fix:\n Perhaps you intended to use the \u2018EmptyCase\u2019 extension\n","message":["Empty list of alternatives in case expression"],"hints":["Perhaps you intended to use the \u2018EmptyCase\u2019 extension"]} +{"version":"1.3","ghcVersion":"ghc-9.13.20250627","span":{"file":"json.hs","start":{"line":9,"column":11},"end":{"line":9,"column":21}},"relatedSpans":[],"severity":"Error","code":48010,"rendered":"json.hs:9:11: error: [GHC-48010]\n Empty list of alternatives in case expression\n Suggested fix:\n Perhaps you intended to use the \u2018EmptyCase\u2019 extension\n","message":["Empty list of alternatives in case expression"],"hints":["Perhaps you intended to use the \u2018EmptyCase\u2019 extension"]} ===================================== testsuite/tests/driver/json2.stderr ===================================== @@ -1,2 +1,2 @@ -{"span":null,"doc":"-ddump-json is deprecated: Use `-fdiagnostics-as-json` instead","messageClass":"MCDiagnostic SevWarning WarningWithFlags Opt_WarnDeprecatedFlags :| [] Just GHC-53692"} +{"span":null,"doc":"-ddump-json is deprecated: Use `-fdiagnostics-as-json` instead","messageClass":"MCDiagnostic SevWarning WarningWithFlags Opt_WarnDeprecatedFlags :| [] Just GHC-53692 []"} {"span":null,"doc":"TYPE SIGNATURES\n foo :: forall a. a -> a\nDependent modules: []\nDependent packages: [(normal, base-4.21.0.0)]","messageClass":"MCOutput"} ===================================== testsuite/tests/driver/json_dump.stderr ===================================== @@ -1,2 +1,2 @@ -{"span":null,"doc":"-ddump-json is deprecated: Use `-fdiagnostics-as-json` instead","messageClass":"MCDiagnostic SevWarning WarningWithFlags Opt_WarnDeprecatedFlags :| [] Just GHC-53692"} -{"span":{"file":"json_dump.hs","startLine":6,"startCol":7,"endLine":6,"endCol":8},"doc":"\u2022 No instance for \u2018Num (a -> a)\u2019 arising from the literal \u20185\u2019\n (maybe you haven't applied a function to enough arguments?)\n\u2022 In the expression: 5\n In an equation for \u2018id1\u2019: id1 = 5","messageClass":"MCDiagnostic SevError ErrorWithoutFlag Just GHC-39999"} +{"span":null,"doc":"-ddump-json is deprecated: Use `-fdiagnostics-as-json` instead","messageClass":"MCDiagnostic SevWarning WarningWithFlags Opt_WarnDeprecatedFlags :| [] Just GHC-53692 []"} +{"span":{"file":"json_dump.hs","startLine":6,"startCol":7,"endLine":6,"endCol":8},"doc":"\u2022 No instance for \u2018Num (a -> a)\u2019 arising from the literal \u20185\u2019\n (maybe you haven't applied a function to enough arguments?)\n\u2022 In the expression: 5\n In an equation for \u2018id1\u2019: id1 = 5","messageClass":"MCDiagnostic SevError ErrorWithoutFlag Just GHC-39999 []"} ===================================== testsuite/tests/driver/json_warn.stderr ===================================== @@ -1,2 +1,2 @@ -{"version":"1.2","ghcVersion":"ghc-9.13.20250627","span":{"file":"json_warn.hs","start":{"line":4,"column":3},"end":{"line":4,"column":4}},"severity":"Warning","code":40910,"rendered":"json_warn.hs:4:3: warning: [GHC-40910] [-Wunused-matches (in -Wextra)]\n Defined but not used: \u2018x\u2019\n","message":["Defined but not used: \u2018x\u2019"],"hints":[],"reason":{"flags":["unused-matches"]}} -{"version":"1.2","ghcVersion":"ghc-9.13.20250627","span":{"file":"json_warn.hs","start":{"line":7,"column":5},"end":{"line":7,"column":9}},"severity":"Warning","code":63394,"rendered":"json_warn.hs:7:5: warning: [GHC-63394] [-Wx-partial (in -Wextended-warnings)]\n In the use of \u2018head\u2019\n (imported from Prelude, but defined in GHC.Internal.List):\n \"This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use \"Data.List.NonEmpty\".\"\n","message":["In the use of \u2018head\u2019\n(imported from Prelude, but defined in GHC.Internal.List):\n\"This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use \"Data.List.NonEmpty\".\""],"hints":[],"reason":{"category":"x-partial"}} +{"version":"1.3","ghcVersion":"ghc-9.13.20250627","span":{"file":"json_warn.hs","start":{"line":4,"column":3},"end":{"line":4,"column":4}},"relatedSpans":[],"severity":"Warning","code":40910,"rendered":"json_warn.hs:4:3: warning: [GHC-40910] [-Wunused-matches (in -Wextra)]\n Defined but not used: \u2018x\u2019\n","message":["Defined but not used: \u2018x\u2019"],"hints":[],"reason":{"flags":["unused-matches"]}} +{"version":"1.3","ghcVersion":"ghc-9.13.20250627","span":{"file":"json_warn.hs","start":{"line":7,"column":5},"end":{"line":7,"column":9}},"relatedSpans":[],"severity":"Warning","code":63394,"rendered":"json_warn.hs:7:5: warning: [GHC-63394] [-Wx-partial (in -Wextended-warnings)]\n In the use of \u2018head\u2019\n (imported from Prelude, but defined in GHC.Internal.List):\n \"This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use \"Data.List.NonEmpty\".\"\n","message":["In the use of \u2018head\u2019\n(imported from Prelude, but defined in GHC.Internal.List):\n\"This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use \"Data.List.NonEmpty\".\""],"hints":[],"reason":{"category":"x-partial"}} View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/a1a2259b9a4cab30faa471a2060cb9a... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/a1a2259b9a4cab30faa471a2060cb9a... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Simon Jakobi (@sjakobi2)