github.com/jhump/protocompile@v0.0.0-20221021153901-4f6f732835e8/linker/linker.go (about) 1 package linker 2 3 import ( 4 "fmt" 5 6 "google.golang.org/protobuf/proto" 7 "google.golang.org/protobuf/reflect/protoreflect" 8 9 "github.com/jhump/protocompile/parser" 10 "github.com/jhump/protocompile/reporter" 11 ) 12 13 // Link handles linking a parsed descriptor proto into a fully-linked descriptor. 14 // If the given parser.Result has imports, they must all be present in the given 15 // dependencies. 16 // 17 // The symbols value is optional and may be nil. If it is not nil, it must be the 18 // same instance used to create and link all of the given result's dependencies 19 // (or otherwise already have all dependencies imported). Otherwise, linking may 20 // fail with spurious errors resolving symbols. 21 // 22 // The handler value is used to report any link errors. If any such errors are 23 // reported, this function returns a non-nil error. The Result value returned 24 // also implements protoreflect.FileDescriptor. 25 // 26 // Note that linking does NOT interpret options. So options messages in the 27 // returned value have all values stored in UninterpretedOptions fields. 28 func Link(parsed parser.Result, dependencies Files, symbols *Symbols, handler *reporter.Handler) (Result, error) { 29 if symbols == nil { 30 symbols = &Symbols{} 31 } 32 prefix := parsed.Proto().GetPackage() 33 if prefix != "" { 34 prefix += "." 35 } 36 37 for _, imp := range parsed.Proto().Dependency { 38 dep := dependencies.FindFileByPath(imp) 39 if dep == nil { 40 return nil, fmt.Errorf("dependencies is missing import %q", imp) 41 } 42 if err := symbols.Import(dep, handler); err != nil { 43 return nil, err 44 } 45 } 46 47 r := &result{ 48 Result: parsed, 49 deps: dependencies, 50 descriptors: map[proto.Message]protoreflect.Descriptor{}, 51 descriptorPool: map[string]proto.Message{}, 52 usedImports: map[string]struct{}{}, 53 prefix: prefix, 54 } 55 56 // First, we put all symbols into a single pool, which lets us ensure there 57 // are no duplicate symbols and will also let us resolve and revise all type 58 // references in next step. 59 if err := symbols.importResult(r, true, false, handler); err != nil { 60 return nil, err 61 } 62 63 // After we've populated the pool, we can now try to resolve all type 64 // references. All references must be checked for correct type, any fields 65 // with enum types must be corrected (since we parse them as if they are 66 // message references since we don't actually know message or enum until 67 // link time), and references will be re-written to be fully-qualified 68 // references (e.g. start with a dot "."). 69 if err := r.resolveReferences(handler, symbols); err != nil { 70 return nil, err 71 } 72 73 return r, handler.Error() 74 } 75 76 // Result is the result of linking. This is a protoreflect.FileDescriptor, but 77 // with some additional methods for exposing additional information, such as the 78 // for accessing the input AST or file descriptor. 79 // 80 // It also provides Resolve* methods, for looking up enums, messages, and 81 // extensions that are available to the protobuf source file this result 82 // represents. An element is "available" if it meets any of the following 83 // criteria: 84 // 1. The element is defined in this file itself. 85 // 2. The element is defined in a file that is directly imported by this file. 86 // 3. The element is "available" to a file that is directly imported by this 87 // file as a public import. 88 // Other elements, even if in the transitive closure of this file, are not 89 // available and thus won't be returned by these methods. 90 type Result interface { 91 File 92 parser.Result 93 // ResolveEnumType returns an enum descriptor for the given named enum that 94 // is available in this file. If no such element is available or if the 95 // named element is not an enum, nil is returned. 96 ResolveEnumType(protoreflect.FullName) protoreflect.EnumDescriptor 97 // ResolveMessageType returns a message descriptor for the given named 98 // message that is available in this file. If no such element is available 99 // or if the named element is not a message, nil is returned. 100 ResolveMessageType(protoreflect.FullName) protoreflect.MessageDescriptor 101 // ResolveExtension returns an extension descriptor for the given named 102 // extension that is available in this file. If no such element is available 103 // or if the named element is not an extension, nil is returned. 104 ResolveExtension(protoreflect.FullName) protoreflect.ExtensionTypeDescriptor 105 // ValidateExtensions runs some validation checks on extensions that can only 106 // be done after files are linked and options are interpreted. Any errors or 107 // warnings encountered will be reported via the given handler. If any error 108 // is reported, this function returns a non-nil error. 109 ValidateExtensions(handler *reporter.Handler) error 110 // CheckForUnusedImports is used to report warnings for unused imports. This 111 // should be called after options have been interpreted. Otherwise, the logic 112 // could incorrectly report imports as unused if the only symbol used were a 113 // custom option. 114 CheckForUnusedImports(handler *reporter.Handler) 115 } 116 117 // ErrorUnusedImport may be passed to a warning reporter when an unused 118 // import is detected. The error the reporter receives will be wrapped 119 // with source position that indicates the file and line where the import 120 // statement appeared. 121 type ErrorUnusedImport interface { 122 error 123 UnusedImport() string 124 } 125 126 type errUnusedImport string 127 128 func (e errUnusedImport) Error() string { 129 return fmt.Sprintf("import %q not used", string(e)) 130 } 131 132 func (e errUnusedImport) UnusedImport() string { 133 return string(e) 134 }