cgo: improve diagnostics
This makes CGo-emitted diagnostics very similar to regular errors emitted while parsing/typechecking a package. It's not complete, but after introducing some errors in testdata/cgo, this is the resulting output: # ./testdata/cgo/ testdata/cgo/main.h:18:11: error: a parameter list without types is only allowed in a function definition testdata/cgo/main.go:5:10: note: in file included from testdata/cgo/main.go!cgo.c:2: testdata/cgo/main.go:6:19: error: expected identifier or '(' Previously, this was the output: /home/ayke/src/github.com/tinygo-org/tinygo/testdata/cgo/main.h:18:11: error: a parameter list without types is only allowed in a function definition cgo-fake.c:3:19: error: expected identifier or '(' # ./testdata/cgo/ cgo: libclang cannot parse fragment
Этот коммит содержится в:
родитель
e5029c63d1
коммит
078dd9ff52
3 изменённых файлов: 70 добавлений и 13 удалений
|
@ -16,6 +16,7 @@ import (
|
||||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||||
type fileInfo struct {
|
type fileInfo struct {
|
||||||
*ast.File
|
*ast.File
|
||||||
|
*Package
|
||||||
filename string
|
filename string
|
||||||
functions map[string]*functionInfo
|
functions map[string]*functionInfo
|
||||||
globals map[string]*globalInfo
|
globals map[string]*globalInfo
|
||||||
|
@ -78,9 +79,10 @@ typedef unsigned long long _Cgo_ulonglong;
|
||||||
|
|
||||||
// processCgo extracts the `import "C"` statement from the AST, parses the
|
// processCgo extracts the `import "C"` statement from the AST, parses the
|
||||||
// comment with libclang, and modifies the AST to use this information.
|
// comment with libclang, and modifies the AST to use this information.
|
||||||
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) error {
|
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error {
|
||||||
info := &fileInfo{
|
info := &fileInfo{
|
||||||
File: f,
|
File: f,
|
||||||
|
Package: p,
|
||||||
filename: filename,
|
filename: filename,
|
||||||
functions: map[string]*functionInfo{},
|
functions: map[string]*functionInfo{},
|
||||||
globals: map[string]*globalInfo{},
|
globals: map[string]*globalInfo{},
|
||||||
|
@ -114,9 +116,10 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
|
||||||
// source location.
|
// source location.
|
||||||
info.importCPos = spec.Path.ValuePos
|
info.importCPos = spec.Path.ValuePos
|
||||||
|
|
||||||
err = info.parseFragment(cgoComment+cgoTypes, cflags)
|
pos := info.fset.PositionFor(genDecl.Doc.Pos(), true)
|
||||||
if err != nil {
|
errs := info.parseFragment(cgoComment+cgoTypes, cflags, pos.Filename, pos.Line)
|
||||||
return err
|
if errs != nil {
|
||||||
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove this import declaration.
|
// Remove this import declaration.
|
||||||
|
|
|
@ -4,9 +4,10 @@ package loader
|
||||||
// modification. It does not touch the AST itself.
|
// modification. It does not touch the AST itself.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/scanner"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -22,11 +23,19 @@ import "C"
|
||||||
|
|
||||||
var globalFileInfo *fileInfo
|
var globalFileInfo *fileInfo
|
||||||
|
|
||||||
func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
|
var diagnosticSeverity = [...]string{
|
||||||
index := C.clang_createIndex(0, 1)
|
C.CXDiagnostic_Ignored: "ignored",
|
||||||
|
C.CXDiagnostic_Note: "note",
|
||||||
|
C.CXDiagnostic_Warning: "warning",
|
||||||
|
C.CXDiagnostic_Error: "error",
|
||||||
|
C.CXDiagnostic_Fatal: "fatal",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilename string, posLine int) []error {
|
||||||
|
index := C.clang_createIndex(0, 0)
|
||||||
defer C.clang_disposeIndex(index)
|
defer C.clang_disposeIndex(index)
|
||||||
|
|
||||||
filenameC := C.CString("cgo-fake.c")
|
filenameC := C.CString(posFilename+"!cgo.c")
|
||||||
defer C.free(unsafe.Pointer(filenameC))
|
defer C.free(unsafe.Pointer(filenameC))
|
||||||
|
|
||||||
fragmentC := C.CString(fragment)
|
fragmentC := C.CString(fragment)
|
||||||
|
@ -61,8 +70,53 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
|
||||||
}
|
}
|
||||||
defer C.clang_disposeTranslationUnit(unit)
|
defer C.clang_disposeTranslationUnit(unit)
|
||||||
|
|
||||||
if C.clang_getNumDiagnostics(unit) != 0 {
|
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
||||||
return errors.New("cgo: libclang cannot parse fragment")
|
errs := []error{}
|
||||||
|
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
||||||
|
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||||
|
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
||||||
|
location := C.clang_getDiagnosticLocation(diagnostic)
|
||||||
|
var file C.CXFile
|
||||||
|
var line C.unsigned
|
||||||
|
var column C.unsigned
|
||||||
|
var offset C.unsigned
|
||||||
|
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
|
||||||
|
filename := getString(C.clang_getFileName(file))
|
||||||
|
if filename == posFilename+"!cgo.c" {
|
||||||
|
// Adjust errors from the `import "C"` snippet.
|
||||||
|
// Note: doesn't adjust filenames inside the error message
|
||||||
|
// itself.
|
||||||
|
filename = posFilename
|
||||||
|
line += C.uint(posLine)
|
||||||
|
offset = 0 // hard to calculate
|
||||||
|
} else if filepath.IsAbs(filename) {
|
||||||
|
// Relative paths for readability, like other Go parser errors.
|
||||||
|
relpath, err := filepath.Rel(info.Program.Dir, filename)
|
||||||
|
if err == nil {
|
||||||
|
filename = relpath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errs = append(errs, &scanner.Error{
|
||||||
|
Pos: token.Position{
|
||||||
|
Filename: filename,
|
||||||
|
Offset: int(offset),
|
||||||
|
Line: int(line),
|
||||||
|
Column: int(column),
|
||||||
|
},
|
||||||
|
Msg: severity + ": " + spelling,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := 0; i < numDiagnostics; i++ {
|
||||||
|
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
||||||
|
addDiagnostic(diagnostic)
|
||||||
|
|
||||||
|
// Child diagnostics (like notes on redefinitions).
|
||||||
|
diagnostics := C.clang_getChildDiagnostics(diagnostic)
|
||||||
|
for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
|
||||||
|
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalFileInfo != nil {
|
if globalFileInfo != nil {
|
||||||
|
|
|
@ -299,9 +299,9 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
|
errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
|
||||||
if err != nil {
|
if errs != nil {
|
||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, errs...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче