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}