blob: 123c2fb7787d08a418b8cf5a0fc10d3a2befa4fa [file] [log] [blame]
/*
* Copyright 2019 The Kythe Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package indexer
import (
"fmt"
"go/types"
"log"
"strings"
"kythe.io/kythe/go/util/schema/facts"
"github.com/golang/protobuf/proto"
cpb "kythe.io/kythe/proto/common_go_proto"
spb "kythe.io/kythe/proto/storage_go_proto"
)
// MarkedSource returns a MarkedSource message describing obj.
// See: http://www.kythe.io/docs/schema/marked-source.html.
func (pi *PackageInfo) MarkedSource(obj types.Object) *cpb.MarkedSource {
ms := &cpb.MarkedSource{
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_IDENTIFIER,
PreText: objectName(obj),
}},
}
// Include the package name as context, and for objects that hang off a
// named struct or interface, a label for that type.
//
// For example, given
// package p
// var v int // context is "p"
// type s struct { v int } // context is "p.s"
// func (v) f(x int) {}
// ^ ^--------------- context is "p.v.f"
// \----------------- context is "p.v"
//
// The tree structure is:
//
// (box)
// |
// (ctx)-----+-------(id)
// | |
// +----"."----+(".") name
// | |
// (id) pkg type
//
if ctx := pi.typeContext(obj); len(ctx) != 0 {
ms.Child = append([]*cpb.MarkedSource{{
Kind: cpb.MarkedSource_CONTEXT,
PostChildText: ".",
AddFinalListToken: true,
Child: ctx,
}}, ms.Child...)
}
// Handle types with "interesting" superstructure specially.
switch t := obj.(type) {
case *types.Func:
// For functions we include the parameters and return values, and for
// methods the receiver.
//
// Methods: func (R) Name(p1, ...) (r0, ...)
// Functions: func Name(p0, ...) (r0, ...)
fn := &cpb.MarkedSource{
Kind: cpb.MarkedSource_BOX,
Child: []*cpb.MarkedSource{{PreText: "func "}},
}
sig := t.Type().(*types.Signature)
firstParam := 0
if recv := sig.Recv(); recv != nil {
// Parenthesized receiver type, e.g. (R).
fn.Child = append(fn.Child, &cpb.MarkedSource{
Kind: cpb.MarkedSource_PARAMETER,
PreText: "(",
PostText: ") ",
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_TYPE,
PreText: typeName(recv.Type()),
}},
})
firstParam = 1
}
fn.Child = append(fn.Child, ms)
// If there are no parameters, the lookup will not produce anything.
// Ensure when this happens we still get parentheses for notational
// purposes.
if sig.Params().Len() == 0 {
fn.Child = append(fn.Child, &cpb.MarkedSource{
Kind: cpb.MarkedSource_PARAMETER,
PreText: "()",
})
} else {
fn.Child = append(fn.Child, &cpb.MarkedSource{
Kind: cpb.MarkedSource_PARAMETER_LOOKUP_BY_PARAM,
PreText: "(",
PostChildText: ", ",
PostText: ")",
LookupIndex: uint32(firstParam),
})
}
if res := sig.Results(); res != nil && res.Len() > 0 {
rms := &cpb.MarkedSource{Kind: cpb.MarkedSource_TYPE, PreText: " "}
if res.Len() > 1 {
// If there is more than one result type, parenthesize.
rms.PreText = " ("
rms.PostText = ")"
rms.PostChildText = ", "
}
for i := 0; i < res.Len(); i++ {
rms.Child = append(rms.Child, &cpb.MarkedSource{
PreText: objectName(res.At(i)),
})
}
fn.Child = append(fn.Child, rms)
}
ms = fn
case *types.Var:
// For variables and fields, include the type.
repl := &cpb.MarkedSource{
Kind: cpb.MarkedSource_BOX,
PostChildText: " ",
Child: []*cpb.MarkedSource{
ms,
{Kind: cpb.MarkedSource_LOOKUP_BY_TYPED},
},
}
ms = repl
case *types.TypeName:
// For named types, include the underlying type.
repl := &cpb.MarkedSource{
Kind: cpb.MarkedSource_BOX,
PostChildText: " ",
Child: []*cpb.MarkedSource{
{PreText: "type"},
ms,
{Kind: cpb.MarkedSource_TYPE, PreText: typeName(t.Type().Underlying())},
},
}
ms = repl
default:
// TODO(fromberger): Handle other variations from go/types.
}
return ms
}
// objectName returns a human-readable name for obj if one can be inferred. If
// the object has its own non-blank name, that is used; otherwise if the object
// is of a named type, that type's name is used. Otherwise the result is "_".
func objectName(obj types.Object) string {
if name := obj.Name(); name != "" {
return name // the object's given name
} else if name := typeName(obj.Type()); name != "" {
return name // the object's type's name
}
return "_" // not sure what to call it
}
// typeName returns a human readable name for typ.
func typeName(typ types.Type) string {
switch t := typ.(type) {
case *types.Named:
return t.Obj().Name()
case *types.Basic:
return t.Name()
case *types.Struct:
return "struct {...}"
case *types.Interface:
return "interface {...}"
case *types.Pointer:
return "*" + typeName(t.Elem())
}
return typ.String()
}
// typeContext returns the package, type, and function context identifiers that
// qualify the name of obj, if any are applicable. The result is empty if there
// are no appropriate qualifiers.
func (pi *PackageInfo) typeContext(obj types.Object) []*cpb.MarkedSource {
var ms []*cpb.MarkedSource
addID := func(s string) {
ms = append(ms, &cpb.MarkedSource{
Kind: cpb.MarkedSource_IDENTIFIER,
PreText: s,
})
}
for cur := pi.owner[obj]; cur != nil; cur = pi.owner[cur] {
if t, ok := cur.(interface {
Name() string
}); ok {
addID(t.Name())
} else {
addID(typeName(cur.Type()))
}
}
if pkg := obj.Pkg(); pkg != nil {
addID(pi.importPath(pkg))
}
for i, j := 0, len(ms)-1; i < j; {
ms[i], ms[j] = ms[j], ms[i]
i++
j--
}
return ms
}
// emitCode emits a code fact for the specified marked source message on the
// target, or logs a diagnostic.
func (e *emitter) emitCode(target *spb.VName, ms *cpb.MarkedSource) {
if ms != nil {
bits, err := proto.Marshal(ms)
if err != nil {
log.Printf("ERROR: Unable to marshal marked source: %v", err)
return
}
e.writeFact(target, facts.Code, string(bits))
}
}
func (e *emitter) emitPackageMarkedSource(pi *PackageInfo) {
if !e.opts.emitMarkedSource() || len(pi.Files) == 0 {
return
}
ipath := pi.ImportPath
ms := &cpb.MarkedSource{
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_IDENTIFIER,
PreText: ipath,
}},
}
if p := strings.LastIndex(ipath, "/"); p > 0 {
ms.Child[0].PreText = ipath[p+1:]
ms.Child = append([]*cpb.MarkedSource{{
Kind: cpb.MarkedSource_CONTEXT,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_IDENTIFIER,
PreText: ipath[:p],
}},
PostChildText: "/",
}}, ms.Child...)
}
e.emitCode(pi.VName, ms)
}
func (e *emitter) emitBuiltinMarkedSource(b *spb.VName) {
if e.opts.emitMarkedSource() {
e.emitCode(b, &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
PreText: strings.TrimSuffix(b.Signature, "#builtin"),
})
}
}
var (
sliceTAppMS = &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 1,
}},
PreText: "[]",
}
pointerTAppMS = &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 1,
}},
PreText: "*",
}
mapTAppMS = &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
PreText: "map",
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_BOX,
PreText: "[",
PostText: "]",
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 1,
}},
}, {
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 2,
}},
}
tupleTAppMS = &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_PARAMETER_LOOKUP_BY_PARAM,
LookupIndex: 1,
PostChildText: ", ",
}},
PreText: "(",
PostText: ")",
}
variadicTAppMS = &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 1,
}},
PreText: "...",
}
chanOmniTAppMS = &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 1,
}},
PreText: "chan ",
}
chanSendTAppMS = &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 1,
}},
PreText: "chan<- ",
}
chanRecvTAppMS = &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 1,
}},
PreText: "<-chan ",
}
)
func arrayTAppMS(length int64) *cpb.MarkedSource {
return &cpb.MarkedSource{
Kind: cpb.MarkedSource_TYPE,
Child: []*cpb.MarkedSource{{
Kind: cpb.MarkedSource_LOOKUP_BY_PARAM,
LookupIndex: 1,
}},
PreText: fmt.Sprintf("[%d]", length),
}
}
func chanTAppMS(dir types.ChanDir) *cpb.MarkedSource {
switch dir {
case types.SendOnly:
return chanSendTAppMS
case types.RecvOnly:
return chanRecvTAppMS
default:
return chanOmniTAppMS
}
}