001package gudusoft.gsqlparser.ir.semantic; 002 003import gudusoft.gsqlparser.nodes.TParseTreeNode; 004 005import java.util.Objects; 006 007/** 008 * Structured rejection from the SemanticIR builder. Carries a stable 009 * {@link DiagnosticCode}, a {@link Severity}, the rendered 010 * user-visible {@link #getMessage() message}, and an optional 011 * {@link SourceSpan} pointing at the offending AST node. 012 * 013 * <p>Diagnostics are constructed via the {@link #error} factory 014 * methods and wrapped in 015 * {@code SemanticIRBuilder.SemanticIRBuildException} when the builder 016 * cannot continue. External callers should pattern-match on 017 * {@link #getCode()} rather than parsing {@link #getMessage()} 018 * substrings — message text remains user-visible English and may 019 * change without notice; the enum values are the public contract. 020 */ 021public final class Diagnostic { 022 023 private final DiagnosticCode code; 024 private final Severity severity; 025 private final String message; 026 private final SourceSpan span; 027 028 private Diagnostic(DiagnosticCode code, Severity severity, 029 String message, SourceSpan span) { 030 this.code = Objects.requireNonNull(code, "code"); 031 this.severity = Objects.requireNonNull(severity, "severity"); 032 this.message = Objects.requireNonNull(message, "message"); 033 this.span = span; 034 } 035 036 /** 037 * Construct an {@link Severity#ERROR ERROR}-severity diagnostic 038 * with no span anchor. 039 */ 040 public static Diagnostic error(DiagnosticCode code, String message) { 041 return new Diagnostic(code, Severity.ERROR, message, null); 042 } 043 044 /** 045 * Construct an {@link Severity#ERROR ERROR}-severity diagnostic 046 * whose span is derived from {@code anchor} via 047 * {@link SourceSpan#of(TParseTreeNode)}. Null-safe: when 048 * {@code anchor} (or its boundary tokens) is {@code null}, the 049 * resulting span is {@code null}. 050 */ 051 public static Diagnostic error(DiagnosticCode code, String message, 052 TParseTreeNode anchor) { 053 return new Diagnostic(code, Severity.ERROR, message, SourceSpan.of(anchor)); 054 } 055 056 /** 057 * Construct an {@link Severity#ERROR ERROR}-severity diagnostic 058 * with an explicit (already-computed) source span. Most callers 059 * should prefer {@link #error(DiagnosticCode, String, TParseTreeNode)} 060 * which derives the span from an AST node. 061 */ 062 public static Diagnostic errorWithSpan(DiagnosticCode code, String message, 063 SourceSpan span) { 064 return new Diagnostic(code, Severity.ERROR, message, span); 065 } 066 067 /** 068 * Slice 77 — construct a {@link Severity#WARN WARN}-severity 069 * diagnostic with no span anchor. Warnings are advisory: the 070 * analyzer continues, the IR is built, and 071 * {@code AnalysisResult.isSuccessful()} still returns {@code true}. 072 */ 073 public static Diagnostic warn(DiagnosticCode code, String message) { 074 return new Diagnostic(code, Severity.WARN, message, null); 075 } 076 077 /** 078 * Slice 77 — construct a {@link Severity#WARN WARN}-severity 079 * diagnostic whose span is derived from {@code anchor}. 080 */ 081 public static Diagnostic warn(DiagnosticCode code, String message, 082 TParseTreeNode anchor) { 083 return new Diagnostic(code, Severity.WARN, message, SourceSpan.of(anchor)); 084 } 085 086 public DiagnosticCode getCode() { 087 return code; 088 } 089 090 public Severity getSeverity() { 091 return severity; 092 } 093 094 public String getMessage() { 095 return message; 096 } 097 098 /** @return the source span, or {@code null} when no AST anchor was available. */ 099 public SourceSpan getSpan() { 100 return span; 101 } 102 103 @Override 104 public boolean equals(Object o) { 105 if (this == o) return true; 106 if (!(o instanceof Diagnostic)) return false; 107 Diagnostic that = (Diagnostic) o; 108 return code == that.code 109 && severity == that.severity 110 && message.equals(that.message) 111 && Objects.equals(span, that.span); 112 } 113 114 @Override 115 public int hashCode() { 116 return Objects.hash(code, severity, message, span); 117 } 118 119 @Override 120 public String toString() { 121 StringBuilder sb = new StringBuilder(); 122 sb.append('[').append(code.name()).append("] ").append(message); 123 if (span != null) { 124 sb.append(" at ").append(span); 125 } 126 return sb.toString(); 127 } 128}