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
Этот коммит содержится в:
Ayke van Laethem 2019-04-10 15:51:44 +02:00 коммит произвёл Ron Evans
родитель 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)