1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 package specdoc
46
47 import (
48 "bytes"
49 "cmp"
50 "fmt"
51 "go/ast"
52 "go/format"
53 "go/parser"
54 "go/token"
55 "path/filepath"
56 "slices"
57 "strings"
58
59 "simd/archsimd/_gen/specgen"
60 )
61
62
63 type Options struct {
64
65
66
67
68
69
70
71
72 RejectUnknown bool
73
74
75
76
77
78
79
80
81
82
83
84
85 AllowDocRewrite bool
86
87
88
89
90
91
92 AllowNameMismatches bool
93
94
95
96
97 NoFillDoc bool
98
99
100
101
102
103
104 NoFillNames bool
105
106
107
108 Filename string
109 }
110
111 type textEdit struct {
112 start int
113 end int
114 text string
115 }
116
117
118
119 func Filler(idx *specgen.Index, opts Options) func(relPath string, isGo bool, content []byte) ([]byte, error) {
120 return func(relPath string, isGo bool, src []byte) ([]byte, error) {
121 if !isGo {
122 return src, nil
123 }
124 dir := filepath.ToSlash(filepath.Dir(relPath))
125 if dir != "simd" && dir != "simd/archsimd" {
126 return src, nil
127 }
128 fileOpts := opts
129 fileOpts.Filename = filepath.ToSlash(relPath)
130 return Fill(src, idx, fileOpts)
131 }
132 }
133
134
135
136
137
138 func Fill(src []byte, spec *specgen.Index, opts Options) ([]byte, error) {
139 fset := token.NewFileSet()
140 filename := opts.Filename
141 if filename == "" {
142 filename = "<unknown>"
143 }
144 file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
145 if err != nil {
146 return src, fmt.Errorf("parsing %s: %w", filename, err)
147 }
148
149 var report Report
150 var edits []textEdit
151
152 for _, d := range file.Decls {
153 fd, ok := d.(*ast.FuncDecl)
154 if !ok || !fd.Name.IsExported() {
155 continue
156 }
157
158 decl := Decl{
159 Pos: fset.Position(fd.Pos()),
160 Recv: recvTypeName(fd.Recv),
161 Name: fd.Name.Name,
162 }
163
164
165 specFn := spec.Lookup(decl.Recv, decl.Name)
166 if specFn == nil {
167 if opts.RejectUnknown {
168 report.UnknownDecls = append(report.UnknownDecls, UnknownDecl{decl})
169 }
170 continue
171 }
172
173
174
175
176
177
178 doc := parseCommentGroup(fd.Doc)
179 if !opts.AllowDocRewrite {
180 for _, p := range doc {
181 if p.Kind == specgen.SpecOwned {
182 report.UnexpectedDocs = append(report.UnexpectedDocs, UnexpectedDoc{decl})
183 break
184 }
185 }
186 }
187
188 var recv specgen.Arg
189 if fd.Recv != nil && len(fd.Recv.List) == 1 {
190 recvs, err := fieldListArgs(fd.Recv)
191 if err != nil {
192 return src, fmt.Errorf("%s: %s receiver: %w", decl.Pos, decl.Name, err)
193 }
194 recv = recvs[0]
195 }
196
197 in, err := fieldListArgs(fd.Type.Params)
198 if err != nil {
199 return src, fmt.Errorf("%s: %s params: %w", decl.Pos, decl.Name, err)
200 }
201 out, err := fieldListArgs(fd.Type.Results)
202 if err != nil {
203 return src, fmt.Errorf("%s: %s results: %w", decl.Pos, decl.Name, err)
204 }
205
206
207 declFn := &specgen.Func{Doc: doc, Recv: recv, Name: decl.Name, In: in, Out: out}
208
209
210 var orderErr error
211 if len(declFn.Doc) > 0 {
212 if _, err := specgen.FormatComment(doc); err != nil {
213 orderErr = err
214 report.DocOrderViolations = append(report.DocOrderViolations, DocOrderViolation{
215 D: decl,
216 Err: orderErr,
217 })
218 }
219 }
220
221
222 nameDetails, typeDetails := compareFuncs(declFn, specFn, opts.NoFillNames)
223 if len(nameDetails) > 0 && !opts.AllowNameMismatches {
224 report.NameMismatches = append(report.NameMismatches, NameMismatch{
225 D: decl,
226 DeclSig: declFn.Signature(),
227 SpecSig: specFn.Signature(),
228 Details: nameDetails,
229 })
230 }
231 if len(typeDetails) > 0 {
232 report.TypeMismatches = append(report.TypeMismatches, TypeMismatch{
233 D: decl,
234 DeclSig: declFn.Signature(),
235 SpecSig: specFn.Signature(),
236 Details: typeDetails,
237 })
238 }
239
240
241 if !opts.NoFillDoc && orderErr == nil {
242 if edit, ok := rewriteDoc(fset, src, fd, doc, specFn.Doc); ok {
243 edits = append(edits, edit)
244 }
245 }
246
247
248 if !opts.NoFillNames && len(nameDetails) == 0 && len(typeDetails) == 0 {
249 if edit, ok := rewriteSignature(fset, src, fd, declFn, specFn); ok {
250 edits = append(edits, edit)
251 }
252 }
253 }
254
255 var repErr error
256 if !report.Empty() {
257 repErr = &report
258 }
259
260 if len(edits) == 0 {
261 return src, repErr
262 }
263
264 rewritten := applyEdits(src, edits)
265 formatted, err := format.Source(rewritten)
266 if err != nil {
267 if repErr != nil {
268 return rewritten, repErr
269 }
270 return rewritten, fmt.Errorf("formatting source: %w", err)
271 }
272 return formatted, repErr
273 }
274
275 func namedArg(arg specgen.Arg) bool {
276 return !(arg.Name == "" || arg.Name == "_")
277 }
278
279
280
281 func compareFuncs(declFn, specFn *specgen.Func, noFillNames bool) (nameDetails, typeDetails []string) {
282 if (declFn.Recv.Type != nil) != (specFn.Recv.Type != nil) {
283
284 panic("cannot compare function and method")
285 }
286
287 compareArg := func(decl, spec specgen.Arg, label string, args ...any) {
288 if namedArg(spec) && decl.Name != spec.Name {
289 if !namedArg(decl) && !noFillNames {
290
291
292 } else {
293 var buf strings.Builder
294 fmt.Fprintf(&buf, label, args...)
295 fmt.Fprintf(&buf, ": API name %q != spec name %q", decl.Name, spec.Name)
296 nameDetails = append(nameDetails, buf.String())
297 }
298 }
299 if decl.Type != spec.Type {
300 var buf strings.Builder
301 fmt.Fprintf(&buf, label, args...)
302 if spec.Name != "" {
303 fmt.Fprintf(&buf, " (%s)", spec.Name)
304 }
305 fmt.Fprintf(&buf, ": API type %s != spec type %s", decl.Type, spec.Type)
306 typeDetails = append(typeDetails, buf.String())
307 }
308 }
309 compareArgs := func(decl, spec []specgen.Arg, label string) {
310 if len(decl) != len(spec) {
311 typeDetails = append(typeDetails, fmt.Sprintf("%s count mismatch: API has %d, spec has %d", label, len(decl), len(spec)))
312 return
313 }
314 for i := range decl {
315 compareArg(decl[i], spec[i], "%s %d", label, i)
316 }
317 }
318
319 if specFn.Recv.Type != nil {
320 compareArg(declFn.Recv, specFn.Recv, "receiver")
321 }
322 compareArgs(declFn.In, specFn.In, "parameter")
323 compareArgs(declFn.Out, specFn.Out, "result")
324
325 return nameDetails, typeDetails
326 }
327
328
329
330 func recvTypeName(fl *ast.FieldList) string {
331 if fl == nil || len(fl.List) == 0 {
332 return ""
333 }
334 t := fl.List[0].Type
335 for {
336 switch tt := t.(type) {
337 case *ast.Ident:
338 return tt.Name
339
340 case *ast.StarExpr:
341 t = tt.X
342 case *ast.ParenExpr:
343 t = tt.X
344 case *ast.IndexExpr:
345 t = tt.X
346 case *ast.IndexListExpr:
347 t = tt.X
348
349 default:
350 return "<unknown>"
351 }
352 }
353 }
354
355
356
357
358 func rewriteDoc(fset *token.FileSet, src []byte, fd *ast.FuncDecl, declDoc, specDoc []specgen.Paragraph) (textEdit, bool) {
359 newParas := append([]specgen.Paragraph(nil), specDoc...)
360 for _, p := range declDoc {
361 if p.Kind == specgen.ImplementationNote || p.Kind == specgen.DirectiveComment {
362 newParas = append(newParas, p)
363 }
364 }
365
366 replacementComment, err := specgen.FormatComment(newParas)
367 if err != nil {
368 return textEdit{}, false
369 }
370
371 if fd.Doc != nil {
372
373 start := fset.Position(fd.Doc.Pos()).Offset
374 end := fset.Position(fd.Doc.End()).Offset + 1
375 if string(src[start:end]) != replacementComment {
376 return textEdit{start: start, end: end, text: replacementComment}, true
377 }
378 } else if replacementComment != "" {
379
380 start := fset.Position(fd.Pos()).Offset
381 return textEdit{start: start, end: start, text: replacementComment}, true
382 }
383
384 return textEdit{}, false
385 }
386
387
388
389 func rewriteSignature(fset *token.FileSet, src []byte, fd *ast.FuncDecl, declFn, specFn *specgen.Func) (textEdit, bool) {
390
391 mergedFn := &specgen.Func{
392 Recv: declFn.Recv,
393 Name: declFn.Name,
394 In: mergeArgs(declFn.In, specFn.In),
395 Out: mergeArgs(declFn.Out, specFn.Out),
396 }
397 newSig := mergedFn.Signature()
398 start := fset.Position(fd.Pos()).Offset
399 end := fset.Position(fd.Type.End()).Offset
400 if string(src[start:end]) != newSig {
401 return textEdit{start: start, end: end, text: newSig}, true
402 }
403 return textEdit{}, false
404 }
405
406 func mergeArgs(declArgs, specArgs []specgen.Arg) []specgen.Arg {
407 merged := slices.Clone(declArgs)
408
409 for i := range merged {
410 if namedArg(specArgs[i]) && !namedArg(merged[i]) {
411 merged[i].Name = specArgs[i].Name
412 }
413 }
414 return merged
415 }
416
417
418
419 func applyEdits(src []byte, edits []textEdit) []byte {
420 if len(edits) == 0 {
421 return src
422 }
423
424
425 slices.SortFunc(edits, func(a, b textEdit) int {
426 return cmp.Compare(a.start, b.start)
427 })
428
429 var out bytes.Buffer
430 last := 0
431 for _, e := range edits {
432 out.Write(src[last:e.start])
433 out.WriteString(e.text)
434 last = e.end
435 }
436 out.Write(src[last:])
437 return out.Bytes()
438 }
439
View as plain text