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  }