Source file src/simd/archsimd/_gen/specdoc/report.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package specdoc
     6  
     7  import (
     8  	"cmp"
     9  	"fmt"
    10  	"go/token"
    11  	"io"
    12  	"slices"
    13  	"strings"
    14  )
    15  
    16  // Finding is an individual finding in a [Report].
    17  type Finding interface {
    18  	String() string
    19  	Decl() Decl
    20  }
    21  
    22  // Report collects all findings from specfill across checked declarations.
    23  // It implements the error interface.
    24  type Report struct {
    25  	TypeMismatches     []TypeMismatch
    26  	NameMismatches     []NameMismatch
    27  	DocOrderViolations []DocOrderViolation
    28  	UnknownDecls       []UnknownDecl
    29  	UnexpectedDocs     []UnexpectedDoc
    30  }
    31  
    32  // Error formats the report as a string, implementing the error interface.
    33  func (r *Report) Error() string {
    34  	var b strings.Builder
    35  	r.Print(&b)
    36  	return b.String()
    37  }
    38  
    39  // Decl identifies an AST declaration and its location in the source code.
    40  type Decl struct {
    41  	Pos  token.Position
    42  	Recv string
    43  	Name string
    44  }
    45  
    46  // Compare orders declarations by source position (filename, line, column,
    47  // offset), breaking ties by receiver and name.
    48  func (d Decl) Compare(other Decl) int {
    49  	if c := cmp.Compare(d.Pos.Filename, other.Pos.Filename); c != 0 {
    50  		return c
    51  	}
    52  	if c := cmp.Compare(d.Pos.Offset, other.Pos.Offset); c != 0 {
    53  		return c
    54  	}
    55  	if c := cmp.Compare(d.Recv, other.Recv); c != 0 {
    56  		return c
    57  	}
    58  	return cmp.Compare(d.Name, other.Name)
    59  }
    60  
    61  // String formats the declaration as "(Recv) Name" or "Name".
    62  func (d Decl) String() string {
    63  	if d.Recv != "" {
    64  		return fmt.Sprintf("(%s) %s", d.Recv, d.Name)
    65  	}
    66  	return d.Name
    67  }
    68  
    69  // TypeMismatch records a disagreement between spec parameter/result types
    70  // and an AST declaration's parameter/result types.
    71  type TypeMismatch struct {
    72  	D       Decl
    73  	DeclSig string
    74  	SpecSig string
    75  	Details []string
    76  }
    77  
    78  func (m TypeMismatch) Decl() Decl { return m.D }
    79  
    80  func (m TypeMismatch) String() string {
    81  	var b strings.Builder
    82  	fmt.Fprintf(&b, "%s: %s: type mismatch\n", m.D.Pos, m.D)
    83  	fmt.Fprintf(&b, "  decl: %s\n", m.DeclSig)
    84  	fmt.Fprintf(&b, "  spec: %s\n", m.SpecSig)
    85  	for _, d := range m.Details {
    86  		fmt.Fprintf(&b, "  - %s\n", d)
    87  	}
    88  	return b.String()
    89  }
    90  
    91  // NameMismatch records a disagreement between spec parameter/result names
    92  // and an AST declaration's parameter/result names.
    93  type NameMismatch struct {
    94  	D       Decl
    95  	DeclSig string
    96  	SpecSig string
    97  	Details []string
    98  }
    99  
   100  func (m NameMismatch) Decl() Decl { return m.D }
   101  
   102  func (m NameMismatch) String() string {
   103  	var b strings.Builder
   104  	fmt.Fprintf(&b, "%s: %s: name mismatch\n", m.D.Pos, m.D)
   105  	fmt.Fprintf(&b, "  decl: %s\n", m.DeclSig)
   106  	fmt.Fprintf(&b, "  spec: %s\n", m.SpecSig)
   107  	for _, d := range m.Details {
   108  		fmt.Fprintf(&b, "  - %s\n", d)
   109  	}
   110  	return b.String()
   111  }
   112  
   113  // DocOrderViolation records a comment ordering violation in an exported
   114  // declaration's doc comment, which would result in non-idempotent rewriting.
   115  type DocOrderViolation struct {
   116  	D   Decl
   117  	Err error
   118  }
   119  
   120  func (v DocOrderViolation) Decl() Decl { return v.D }
   121  
   122  func (v DocOrderViolation) String() string {
   123  	return fmt.Sprintf("%s: %s: %v\n", v.D.Pos, v.D, v.Err)
   124  }
   125  
   126  // UnknownDecl records an exported declaration that has no matching entry in spec.
   127  type UnknownDecl struct {
   128  	D Decl
   129  }
   130  
   131  func (u UnknownDecl) Decl() Decl { return u.D }
   132  
   133  func (u UnknownDecl) String() string {
   134  	return fmt.Sprintf("%s: %s: declaration not in spec\n", u.D.Pos, u.D)
   135  }
   136  
   137  // UnexpectedDoc records an exported declaration containing an existing
   138  // spec-owned doc comment when AllowDocRewrite is false.
   139  type UnexpectedDoc struct {
   140  	D Decl
   141  }
   142  
   143  func (u UnexpectedDoc) Decl() Decl { return u.D }
   144  
   145  func (u UnexpectedDoc) String() string {
   146  	return fmt.Sprintf("%s: %s: declaration contains unexpected doc comment\n", u.D.Pos, u.D)
   147  }
   148  
   149  // Merge combines other into r.
   150  func (r *Report) Merge(other *Report) {
   151  	r.TypeMismatches = append(r.TypeMismatches, other.TypeMismatches...)
   152  	r.NameMismatches = append(r.NameMismatches, other.NameMismatches...)
   153  	r.DocOrderViolations = append(r.DocOrderViolations, other.DocOrderViolations...)
   154  	r.UnknownDecls = append(r.UnknownDecls, other.UnknownDecls...)
   155  	r.UnexpectedDocs = append(r.UnexpectedDocs, other.UnexpectedDocs...)
   156  }
   157  
   158  // Empty reports whether the report contains no items.
   159  func (r Report) Empty() bool {
   160  	return len(r.TypeMismatches) == 0 &&
   161  		len(r.NameMismatches) == 0 &&
   162  		len(r.DocOrderViolations) == 0 &&
   163  		len(r.UnknownDecls) == 0 &&
   164  		len(r.UnexpectedDocs) == 0
   165  }
   166  
   167  func convertAppend[From Finding](ys []Finding, xs []From) []Finding {
   168  	for _, x := range xs {
   169  		ys = append(ys, x)
   170  	}
   171  	return ys
   172  }
   173  
   174  // Findings returns all findings in r as a slice of [Finding].
   175  func (r Report) Findings() []Finding {
   176  	var items []Finding
   177  	items = convertAppend(items, r.TypeMismatches)
   178  	items = convertAppend(items, r.NameMismatches)
   179  	items = convertAppend(items, r.DocOrderViolations)
   180  	items = convertAppend(items, r.UnknownDecls)
   181  	items = convertAppend(items, r.UnexpectedDocs)
   182  	return items
   183  }
   184  
   185  // Print writes the report to w sorted by position. If there are no findings,
   186  // nothing is written.
   187  func (r Report) Print(w io.Writer) {
   188  	items := r.Findings()
   189  	slices.SortStableFunc(items, func(a, b Finding) int {
   190  		if c := a.Decl().Compare(b.Decl()); c != 0 {
   191  			return c
   192  		}
   193  		return cmp.Compare(a.String(), b.String())
   194  	})
   195  
   196  	for _, item := range items {
   197  		fmt.Fprint(w, item.String())
   198  	}
   199  }
   200  

View as plain text