blob: a76e227886d60292277a96a1bb0466e37837fa9c [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package analysis
import (
"fmt"
"strings"
fidlcommon "fidl-lsp/third_party/common"
"fidl-lsp/state"
)
type symbolInfo struct {
name string
definition state.Location
typeInfo Type
}
type Type struct {
// If IsLib is true, other fields are empty.
// TODO: is there a better way to represent this?
IsLib bool
// If IsMethod is true, only Attrs is set.
IsMethod bool
// Set if this type is actually a resolved alias
FromTypeAlias *string
Attrs []Attribute
Kind TypeKind
Array *ArrayTypeInfo
Vector *VectorTypeInfo
String *StringTypeInfo
Handle *HandleTypeInfo
Request *RequestTypeInfo
Primitive *PrimitiveTypeInfo
Identifier *IdentifierTypeInfo
}
type ArrayTypeInfo struct {
ElementType Type
ElementCount uint
}
type VectorTypeInfo struct {
ElementType Type
ElementCount *uint
Nullable bool
}
type StringTypeInfo struct {
ElementCount *uint
Nullable bool
}
type HandleTypeInfo struct {
Subtype HandleSubtype
Rights uint
Nullable bool
}
type HandleSubtype string
const (
Handle HandleSubtype = "handle"
Bti = "bti"
Channel = "channel"
Clock = "clock"
DebugLog = "debuglog"
Event = "event"
Eventpair = "eventpair"
Exception = "exception"
Fifo = "fifo"
Guest = "guest"
Interrupt = "interrupt"
Iommu = "iommu"
Job = "job"
Pager = "pager"
PciDevice = "pcidevice"
Pmt = "pmt"
Port = "port"
Process = "process"
Profile = "profile"
Resource = "resource"
Socket = "socket"
Stream = "stream"
SuspendToken = "suspendtoken"
Thread = "thread"
Time = "timer"
Vcpu = "vcpu"
Vmar = "vmar"
Vmo = "vmo"
)
type RequestTypeInfo struct {
Subtype string
Nullable bool
}
type PrimitiveTypeInfo struct {
Subtype PrimitiveSubtype
}
type PrimitiveSubtype string
const (
Bool PrimitiveSubtype = "bool"
Int8 = "int8"
Int16 = "int16"
Int32 = "int32"
Int64 = "int64"
Uint8 = "uint8"
Uint16 = "uint16"
Uint32 = "uint32"
Uint64 = "uint64"
Float32 = "float32"
Float64 = "float64"
)
// IdentifierTypeInfo is not a perfect analog for `identifier-type` from the
// JSON IR, because it serves as the type for both declarations of identifier
// types and values of identifier types.
//
// For example, if you have declared a struct Foo, and a protocol method that
// takes a Foo, IdentifierTypeInfo will hold the same type information for each
// of the following symbols:
//
// struct Foo {};
// ~~~
// protocol P {
// Method(Foo foo);
// ~~~ ~~~
// };
//
// So it is both a kind of "declaration type" as well as an "identifier type".
// Iff `IsDecl` == true, it is a "declaration type"; otherwise, it is an
// "identifier type", and `Identifier` is the name of a declaration type that
// can be looked up in the symbol map.
type IdentifierTypeInfo struct {
// If IsDecl is true, all the type information is contained in the tagged
// type info objects -- Kind and one of {Bits, Const, etc.}.
// If IsDecl is false, Identifier and Nullable are set, and Identifier is
// a key to the declaration of the identifier type, in the symbolMap.
IsDecl bool
Identifier string
Nullable bool
Kind IdentifierKind
Name string
Bits *BitsTypeInfo
Const *ConstTypeInfo
Enum *EnumTypeInfo
TypeAlias *TypeAliasTypeInfo
}
type IdentifierKind string
const (
BitsKind IdentifierKind = "bits"
ConstKind = "const"
EnumKind = "enum"
ProtocolKind = "protocol"
ServiceKind = "service"
StructKind = "struct"
TableKind = "table"
UnionKind = "union"
TypeAliasKind = "typeAlias"
)
type BitsTypeInfo struct {
Type Type
}
type ConstTypeInfo struct {
Type Type
Value string
}
type EnumTypeInfo struct {
Type PrimitiveSubtype
}
type TypeAliasTypeInfo struct {
Type Type
}
type nameInLibrary struct {
lib *Library // Left nil if uncertain
name fidlcommon.Name
}
func (a *Analyzer) symbolToFullyQualifiedName(fs *state.FileSystem, sym state.Symbol) (nameInLibrary, error) {
// TODO: if we want to support hovering over members (e.g. struct fields,
// method parameters, bits members) or protocol methods, we need to check
// here whether the symbol is namespaced -- whether it is inside a
// declaration.
// If it is, prepend it with that declaration's name. For example, this
// struct field:
//
// struct Foo {
// MyType my_field;
// ~~~~~~~~
// }
//
// Would become "library.name/Foo.my_field", as this is how it's stored in
// the symbol map.
// If `sym` is a local name (not fully-qualified), we create a FQN by
// attaching its library name.
var fqn string
var lib *Library
if !strings.Contains(sym.Name, ".") {
file, err := fs.File(sym.Location.FileID)
if err != nil {
return nameInLibrary{}, fmt.Errorf("could not open file `%s`", sym.Location.FileID)
}
libName, err := state.LibraryOfFile(file)
if err != nil {
return nameInLibrary{}, fmt.Errorf(
"could not find library of symbol `%s` in file `%s`",
sym.Name,
sym.Location.FileID,
)
}
fqn = libName.FullyQualifiedName() + "/" + sym.Name
// We know this symbol's library, so we can return it
if library, ok := a.getLibraryWithFile(libName, sym.Location.FileID); ok {
lib = library
}
} else {
// If the symbol contains '.', we assume it is a fully-qualified name.
i := strings.LastIndex(sym.Name, ".")
fqn = sym.Name[:i] + "/" + sym.Name[i+1:]
}
// Convert `fqn` to a fidlcommon.Name.
name, err := fidlcommon.ReadName(fqn)
if err != nil {
return nameInLibrary{}, fmt.Errorf("could not read fully-qualified name `%s`: %s", fqn, err)
}
return nameInLibrary{lib: lib, name: name}, nil
}
func (a *Analyzer) lookupSymbolInfo(name fidlcommon.Name) (*symbolInfo, error) {
// We don't call getLibraryWithFile here because we don't know for sure
// which library this symbol is part of. Unless the library is declared as a
// dependency of the library that includes this symbol, it couldn't be
// resolved, and we want to be able to resolve the symbol even if a
// dependency isn't declared.
lib, ok := a.getLibrary(name.LibraryName())
if !ok {
return nil, fmt.Errorf("unknown library `%s`", name.LibraryName().FullyQualifiedName())
}
return a.lookupSymbolInfoInLibrary(name, lib)
}
func (a *Analyzer) lookupSymbolInfoInLibrary(name fidlcommon.Name, lib *Library) (*symbolInfo, error) {
if lib.ir == nil {
if err := a.compileLibrary(lib); err != nil {
return nil, fmt.Errorf(
"error importing library `%s`: %s",
name.LibraryName().FullyQualifiedName(),
err,
)
}
}
// Check that we have a symbolMap for this library, and look up this
// symbol's definition location in that symbolMap.
if lib.symbols == nil {
return nil, fmt.Errorf(
"no symbol map for library `%s`",
name.LibraryName().FullyQualifiedName(),
)
}
symInfo, ok := lib.symbols[name.FullyQualifiedName()]
if !ok {
return nil, fmt.Errorf("could not find symbol `%s`", name.FullyQualifiedName())
}
return symInfo, nil
}