diff options
author | Ayke van Laethem <[email protected]> | 2019-04-10 15:51:44 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-04-11 10:11:09 +0200 |
commit | 078dd9ff5236e40bfd530f8418d72916ce111e33 (patch) | |
tree | 4e4fae7f062de33dbd900865e1a9a909e4a15f39 /loader | |
parent | e5029c63d138d11c7e5304c815c4746d72b4ce54 (diff) | |
download | tinygo-078dd9ff5236e40bfd530f8418d72916ce111e33.tar.gz tinygo-078dd9ff5236e40bfd530f8418d72916ce111e33.zip |
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
Diffstat (limited to 'loader')
-rw-r--r-- | loader/cgo.go | 11 | ||||
-rw-r--r-- | loader/libclang.go | 66 | ||||
-rw-r--r-- | loader/loader.go | 6 |
3 files changed, 70 insertions, 13 deletions
diff --git a/loader/cgo.go b/loader/cgo.go index 626d3dbc4..abb5847c6 100644 --- a/loader/cgo.go +++ b/loader/cgo.go @@ -16,6 +16,7 @@ import ( // fileInfo holds all Cgo-related information of a given *ast.File. type fileInfo struct { *ast.File + *Package filename string functions map[string]*functionInfo 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 // 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{ File: f, + Package: p, filename: filename, functions: map[string]*functionInfo{}, globals: map[string]*globalInfo{}, @@ -114,9 +116,10 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro // source location. info.importCPos = spec.Path.ValuePos - err = info.parseFragment(cgoComment+cgoTypes, cflags) - if err != nil { - return err + pos := info.fset.PositionFor(genDecl.Doc.Pos(), true) + errs := info.parseFragment(cgoComment+cgoTypes, cflags, pos.Filename, pos.Line) + if errs != nil { + return errs } // Remove this import declaration. diff --git a/loader/libclang.go b/loader/libclang.go index 3b0ce4f2a..22297f4d0 100644 --- a/loader/libclang.go +++ b/loader/libclang.go @@ -4,9 +4,10 @@ package loader // modification. It does not touch the AST itself. import ( - "errors" "go/ast" + "go/scanner" "go/token" + "path/filepath" "strconv" "strings" "unsafe" @@ -22,11 +23,19 @@ import "C" var globalFileInfo *fileInfo -func (info *fileInfo) parseFragment(fragment string, cflags []string) error { - index := C.clang_createIndex(0, 1) +var diagnosticSeverity = [...]string{ + 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) - filenameC := C.CString("cgo-fake.c") + filenameC := C.CString(posFilename+"!cgo.c") defer C.free(unsafe.Pointer(filenameC)) fragmentC := C.CString(fragment) @@ -61,8 +70,53 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string) error { } defer C.clang_disposeTranslationUnit(unit) - if C.clang_getNumDiagnostics(unit) != 0 { - return errors.New("cgo: libclang cannot parse fragment") + if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { + 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 { diff --git a/loader/loader.go b/loader/loader.go index c804fd54c..2d15a0600 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -299,9 +299,9 @@ func (p *Package) parseFiles() ([]*ast.File, error) { fileErrs = append(fileErrs, err) continue } - err = p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir)) - if err != nil { - fileErrs = append(fileErrs, err) + errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir)) + if errs != nil { + fileErrs = append(fileErrs, errs...) continue } files = append(files, f) |