| // 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" |
| |
| "fidl-lsp/third_party/fidlgen" |
| |
| "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 fidlgen.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 fidlgen.Name. |
| name, err := fidlgen.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 fidlgen.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 fidlgen.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 |
| } |