Merge pull request #902 from google/merge-moe-changes

Merge moe changes
diff --git a/core/src/com/google/inject/Binder.java b/core/src/com/google/inject/Binder.java
index 91f436f..e895759 100644
--- a/core/src/com/google/inject/Binder.java
+++ b/core/src/com/google/inject/Binder.java
@@ -20,6 +20,7 @@
 import com.google.inject.binder.AnnotatedConstantBindingBuilder;
 import com.google.inject.binder.LinkedBindingBuilder;
 import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.Dependency;
 import com.google.inject.spi.Message;
 import com.google.inject.spi.ProvisionListener;
 import com.google.inject.spi.TypeConverter;
@@ -323,6 +324,18 @@
   <T> Provider<T> getProvider(Key<T> key);
 
   /**
+   * Returns the provider used to obtain instances for the given injection key.
+   * The returned provider will be attached to the injection point and will
+   * follow the nullability specified in the dependency.
+   * Additionally, the returned provider will not be valid until the {@link Injector} 
+   * has been created. The provider will throw an {@code IllegalStateException} if you
+   * try to use it beforehand.
+   *
+   * @since 4.0
+   */
+  <T> Provider<T> getProvider(Dependency<T> dependency);
+
+  /**
    * Returns the provider used to obtain instances for the given injection type.
    * The returned provider will not be valid until the {@link Injector} has been
    * created. The provider will throw an {@code IllegalStateException} if you
diff --git a/core/src/com/google/inject/internal/Annotations.java b/core/src/com/google/inject/internal/Annotations.java
index dfc6936..4c994a9 100644
--- a/core/src/com/google/inject/internal/Annotations.java
+++ b/core/src/com/google/inject/internal/Annotations.java
@@ -201,6 +201,17 @@
     return found;
   }
 
+  static boolean containsComponentAnnotation(Annotation[] annotations) {
+    for (Annotation annotation : annotations) {
+      // TODO(user): Should we scope this down to dagger.Component?
+      if (annotation.annotationType().getSimpleName().equals("Component")) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
   /**
    * Checks for the presence of annotations. Caches results because Android doesn't.
    */
@@ -256,7 +267,9 @@
     }
 
     Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, type);
-    if (scopeAnnotation != null) {
+    if (scopeAnnotation != null
+        // We let Dagger Components through to aid migrations.
+        && !containsComponentAnnotation(type.getAnnotations())) {
       errors.withSource(type).scopeAnnotationOnAbstractType(scopeAnnotation, type, source);
     }
   }
diff --git a/core/src/com/google/inject/internal/Errors.java b/core/src/com/google/inject/internal/Errors.java
index 385d4b8..5ce3b42 100644
--- a/core/src/com/google/inject/internal/Errors.java
+++ b/core/src/com/google/inject/internal/Errors.java
@@ -20,11 +20,14 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
+import com.google.common.collect.Sets;
 import com.google.inject.ConfigurationException;
 import com.google.inject.CreationException;
+import com.google.inject.Guice;
 import com.google.inject.Key;
 import com.google.inject.MembersInjector;
 import com.google.inject.Provider;
+import com.google.inject.Provides;
 import com.google.inject.ProvisionException;
 import com.google.inject.Scope;
 import com.google.inject.TypeLiteral;
@@ -53,6 +56,9 @@
 import java.util.Formatter;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * A collection of error messages. If this type is passed as a method parameter, the method is
@@ -71,6 +77,11 @@
  */
 public final class Errors implements Serializable {
 
+  private static final Logger logger = Logger.getLogger(Guice.class.getName());
+
+  private static final Set<Dependency<?>> warnedDependencies =
+      Sets.newSetFromMap(new ConcurrentHashMap<Dependency<?>, Boolean>());
+
 
   /**
    * The root errors object. Used to access the list of error messages.
@@ -602,6 +613,33 @@
       return value;
     }
 
+    // Hack to allow null parameters to @Provides methods, for backwards compatibility.
+    if (dependency.getInjectionPoint().getMember() instanceof Method) {
+      Method annotated = (Method) dependency.getInjectionPoint().getMember();
+      if (annotated.isAnnotationPresent(Provides.class)) {
+        switch (InternalFlags.getNullableProvidesOption()) {
+          case ERROR:
+            break; // break out & let the below exception happen
+          case IGNORE:
+            return value; // user doesn't care about injecting nulls to non-@Nullables.
+          case WARN:
+            // Warn only once, otherwise we spam logs too much.
+            if (!warnedDependencies.add(dependency)) {
+              return value;
+            }
+            logger.log(Level.WARNING,
+                "Guice injected null into parameter {0} of {1} (a {2}), please mark it @Nullable."
+                    + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
+                    + " error.",
+                new Object[] {
+                    dependency.getParameterIndex(),
+                    convert(dependency.getInjectionPoint().getMember()),
+                    convert(dependency.getKey())});
+            return null; // log & exit.
+        }
+      }
+    }
+
     int parameterIndex = dependency.getParameterIndex();
     String parameterName = (parameterIndex != -1)
         ? "parameter " + parameterIndex + " of "
diff --git a/core/src/com/google/inject/internal/InjectorImpl.java b/core/src/com/google/inject/internal/InjectorImpl.java
index eef7d98..d1d028f 100644
--- a/core/src/com/google/inject/internal/InjectorImpl.java
+++ b/core/src/com/google/inject/internal/InjectorImpl.java
@@ -1000,9 +1000,9 @@
     return getProvider(Key.get(type));
   }
 
-  <T> Provider<T> getProviderOrThrow(final Key<T> key, Errors errors) throws ErrorsException {
+  <T> Provider<T> getProviderOrThrow(final Dependency<T> dependency, Errors errors) throws ErrorsException {
+    final Key<T> key = dependency.getKey();
     final BindingImpl<? extends T> binding = getBindingOrThrow(key, errors, JitLimitation.NO_JIT);
-    final Dependency<T> dependency = Dependency.get(key);
 
     return new Provider<T>() {
       public T get() {
@@ -1034,7 +1034,7 @@
   public <T> Provider<T> getProvider(final Key<T> key) {
     Errors errors = new Errors(key);
     try {
-      Provider<T> result = getProviderOrThrow(key, errors);
+      Provider<T> result = getProviderOrThrow(Dependency.get(key), errors);
       errors.throwIfNewErrors(0);
       return result;
     } catch (ErrorsException e) {
diff --git a/core/src/com/google/inject/internal/InternalFlags.java b/core/src/com/google/inject/internal/InternalFlags.java
index 4e0b227..85c07ac 100644
--- a/core/src/com/google/inject/internal/InternalFlags.java
+++ b/core/src/com/google/inject/internal/InternalFlags.java
@@ -33,6 +33,9 @@
   private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING
       = parseCustomClassLoadingOption();
 
+  private static final NullableProvidesOption NULLABLE_PROVIDES
+      = parseNullableProvidesOption(NullableProvidesOption.ERROR);
+
 
   /**
    * The options for Guice stack trace collection.
@@ -56,6 +59,15 @@
     BRIDGE
   }
 
+  public enum NullableProvidesOption {
+    /** Ignore null parameters to @Provides methods. */
+    IGNORE,
+    /** Warn if null parameters are passed to non-@Nullable parameters of provides methods. */
+    WARN,
+    /** Error if null parameters are passed to non-@Nullable parameters of provides parameters */
+    ERROR
+  }
+
   public static IncludeStackTraceOption getIncludeStackTraceOption() {
     return INCLUDE_STACK_TRACES;
   }
@@ -64,6 +76,10 @@
     return CUSTOM_CLASS_LOADING;
   }
 
+  public static NullableProvidesOption getNullableProvidesOption() {
+    return NULLABLE_PROVIDES;
+  }
+
   private static IncludeStackTraceOption parseIncludeStackTraceOption() {
     return getSystemOption("guice_include_stack_traces",
         IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE);
@@ -74,6 +90,11 @@
         CustomClassLoadingOption.BRIDGE, CustomClassLoadingOption.OFF);
   }
 
+  private static NullableProvidesOption parseNullableProvidesOption(
+      NullableProvidesOption defaultValue) {
+    return getSystemOption("guice_check_nullable_provides_params", defaultValue);
+  }
+
   /**
    * Gets the system option indicated by the specified key; runs as a privileged action.
    *
diff --git a/core/src/com/google/inject/internal/LookupProcessor.java b/core/src/com/google/inject/internal/LookupProcessor.java
index 3971818..bf11b83 100644
--- a/core/src/com/google/inject/internal/LookupProcessor.java
+++ b/core/src/com/google/inject/internal/LookupProcessor.java
@@ -48,7 +48,7 @@
   @Override public <T> Boolean visit(ProviderLookup<T> lookup) {
     // ensure the provider can be created
     try {
-      Provider<T> provider = injector.getProviderOrThrow(lookup.getKey(), errors);
+      Provider<T> provider = injector.getProviderOrThrow(lookup.getDependency(), errors);
       lookup.initializeDelegate(provider);
     } catch (ErrorsException e) {
       errors.merge(e.getErrors()); // TODO: source
diff --git a/core/src/com/google/inject/internal/ProviderMethod.java b/core/src/com/google/inject/internal/ProviderMethod.java
index 1a952ed..beaf406 100644
--- a/core/src/com/google/inject/internal/ProviderMethod.java
+++ b/core/src/com/google/inject/internal/ProviderMethod.java
@@ -24,6 +24,7 @@
 import com.google.inject.Key;
 import com.google.inject.PrivateBinder;
 import com.google.inject.Provider;
+import com.google.inject.Provides;
 import com.google.inject.internal.BytecodeGen.Visibility;
 import com.google.inject.internal.util.StackTraceElements;
 import com.google.inject.spi.BindingTargetVisitor;
@@ -52,21 +53,28 @@
   /**
    * Creates a {@link ProviderMethod}.
    *
-   * <p>Unless {@code skipFastClassGeneration} is set, this will use {@link FastClass} to invoke
-   * the actual method, since it is significantly faster.  However, this will fail if the method is
-   * {@code private} or {@code protected}, since fastclass is subject to java access policies.
+   * <p>Unless {@code skipFastClassGeneration} is set, this will use
+   * {@link net.sf.cglib.reflect.FastClass} to invoke the actual method, since it is significantly
+   * faster. However, this will fail if the method is {@code private} or {@code protected}, since
+   * fastclass is subject to java access policies.
    */
   static <T> ProviderMethod<T> create(Key<T> key, Method method, Object instance,
       ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders,
-      Class<? extends Annotation> scopeAnnotation, boolean skipFastClassGeneration) {
+      Class<? extends Annotation> scopeAnnotation, boolean skipFastClassGeneration,
+      Annotation annotation) {
     int modifiers = method.getModifiers();
     /*if[AOP]*/
     if (!skipFastClassGeneration && !Modifier.isPrivate(modifiers)
         && !Modifier.isProtected(modifiers)) {
       try {
         // We use an index instead of FastMethod to save a stack frame.
-        return new FastClassProviderMethod<T>(
-            key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+        return new FastClassProviderMethod<T>(key,
+            method,
+            instance,
+            dependencies,
+            parameterProviders,
+            scopeAnnotation,
+            annotation);
       } catch (net.sf.cglib.core.CodeGenerationException e) {/* fall-through */}
     }
     /*end[AOP]*/
@@ -76,8 +84,13 @@
       method.setAccessible(true);
     }
 
-    return new ReflectionProviderMethod<T>(
-        key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+    return new ReflectionProviderMethod<T>(key,
+        method,
+        instance,
+        dependencies,
+        parameterProviders,
+        scopeAnnotation,
+        annotation);
   }
 
   protected final Object instance;
@@ -88,13 +101,14 @@
   private final ImmutableSet<Dependency<?>> dependencies;
   private final List<Provider<?>> parameterProviders;
   private final boolean exposed;
+  private final Annotation annotation;
 
   /**
    * @param method the method to invoke. It's return type must be the same type as {@code key}.
    */
   private ProviderMethod(Key<T> key, Method method, Object instance,
       ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders,
-      Class<? extends Annotation> scopeAnnotation) {
+      Class<? extends Annotation> scopeAnnotation, Annotation annotation) {
     this.key = key;
     this.scopeAnnotation = scopeAnnotation;
     this.instance = instance;
@@ -102,12 +116,15 @@
     this.method = method;
     this.parameterProviders = parameterProviders;
     this.exposed = method.isAnnotationPresent(Exposed.class);
+    this.annotation = annotation;
   }
 
+  @Override
   public Key<T> getKey() {
     return key;
   }
 
+  @Override
   public Method getMethod() {
     return method;
   }
@@ -117,9 +134,15 @@
     return instance;
   }
   
+  @Override
   public Object getEnclosingInstance() {
     return instance;
   }
+  
+  @Override
+  public Annotation getAnnotation() {
+    return annotation;
+  }
 
   public void configure(Binder binder) {
     binder = binder.withSource(method);
@@ -137,6 +160,7 @@
     }
   }
 
+  @Override
   public T get() {
     Object[] parameters = new Object[parameterProviders.size()];
     for (int i = 0; i < parameters.length; i++) {
@@ -158,10 +182,12 @@
   abstract Object doProvision(Object[] parameters)
       throws IllegalAccessException, InvocationTargetException;
 
+  @Override
   public Set<Dependency<?>> getDependencies() {
     return dependencies;
   }
   
+  @Override
   @SuppressWarnings("unchecked")
   public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor,
       ProviderInstanceBinding<? extends B> binding) {
@@ -172,15 +198,24 @@
   }
 
   @Override public String toString() {
-    return "@Provides " + StackTraceElements.forMember(method);
+    String annotationString = annotation.toString();
+    // Show @Provides w/o the com.google.inject prefix.
+    if (annotation.annotationType() == Provides.class) {
+      annotationString = "@Provides";
+    } else if (annotationString.endsWith("()")) {
+      // Remove the common "()" suffix if there are no values.
+      annotationString = annotationString.substring(0, annotationString.length() - 2);
+    }
+    return annotationString + " " + StackTraceElements.forMember(method);
   }
   
   @Override
   public boolean equals(Object obj) {
     if (obj instanceof ProviderMethod) {
-      ProviderMethod o = (ProviderMethod)obj;
+      ProviderMethod<?> o = (ProviderMethod<?>) obj;
       return method.equals(o.method)
-         && instance.equals(o.instance);
+         && instance.equals(o.instance)
+         && annotation.equals(o.annotation);
     } else {
       return false;
     }
@@ -191,7 +226,7 @@
     // Avoid calling hashCode on 'instance', which is a user-object
     // that might not be expecting it.
     // (We need to call equals, so we do.  But we can avoid hashCode.)
-    return Objects.hashCode(method);
+    return Objects.hashCode(method, annotation);
   }
 
   /*if[AOP]*/
@@ -208,8 +243,15 @@
         Object instance,
         ImmutableSet<Dependency<?>> dependencies,
         List<Provider<?>> parameterProviders,
-        Class<? extends Annotation> scopeAnnotation) {
-      super(key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+        Class<? extends Annotation> scopeAnnotation,
+        Annotation annotation) {
+      super(key,
+          method,
+          instance,
+          dependencies,
+          parameterProviders,
+          scopeAnnotation,
+          annotation);
       // We need to generate a FastClass for the method's class, not the object's class.
       this.fastClass =
           BytecodeGen.newFastClass(method.getDeclaringClass(), Visibility.forMember(method));
@@ -241,8 +283,15 @@
         Object instance,
         ImmutableSet<Dependency<?>> dependencies,
         List<Provider<?>> parameterProviders,
-        Class<? extends Annotation> scopeAnnotation) {
-      super(key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+        Class<? extends Annotation> scopeAnnotation,
+        Annotation annotation) {
+      super(key,
+          method,
+          instance,
+          dependencies,
+          parameterProviders,
+          scopeAnnotation,
+          annotation);
     }
 
     @Override Object doProvision(Object[] parameters) throws IllegalAccessException,
diff --git a/core/src/com/google/inject/internal/ProviderMethodsModule.java b/core/src/com/google/inject/internal/ProviderMethodsModule.java
index 214039d..98eb45d 100644
--- a/core/src/com/google/inject/internal/ProviderMethodsModule.java
+++ b/core/src/com/google/inject/internal/ProviderMethodsModule.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.common.base.Optional;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
@@ -28,7 +29,9 @@
 import com.google.inject.Provider;
 import com.google.inject.Provides;
 import com.google.inject.TypeLiteral;
+import com.google.inject.spi.ModuleAnnotatedMethodScanner;
 import com.google.inject.spi.Dependency;
+import com.google.inject.spi.InjectionPoint;
 import com.google.inject.spi.Message;
 import com.google.inject.util.Modules;
 
@@ -38,7 +41,7 @@
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import java.util.List;
-import java.util.logging.Logger;
+import java.util.Set;
 
 /**
  * Creates bindings to methods annotated with {@literal @}{@link Provides}. Use the scope and
@@ -48,23 +51,46 @@
  * @author jessewilson@google.com (Jesse Wilson)
  */
 public final class ProviderMethodsModule implements Module {
-  private static final Key<Logger> LOGGER_KEY = Key.get(Logger.class);
+
+  private static ModuleAnnotatedMethodScanner PROVIDES_BUILDER =
+      new ModuleAnnotatedMethodScanner() {
+        @Override
+        public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
+            InjectionPoint injectionPoint) {
+          return key;
+        }
+
+        @Override
+        public Set<? extends Class<? extends Annotation>> annotationClasses() {
+          return ImmutableSet.of(Provides.class);
+        }
+      };
 
   private final Object delegate;
   private final TypeLiteral<?> typeLiteral;
   private final boolean skipFastClassGeneration;
+  private final ModuleAnnotatedMethodScanner scanner;
 
-  private ProviderMethodsModule(Object delegate, boolean skipFastClassGeneration) {
+  private ProviderMethodsModule(Object delegate, boolean skipFastClassGeneration,
+      ModuleAnnotatedMethodScanner scanner) {
     this.delegate = checkNotNull(delegate, "delegate");
     this.typeLiteral = TypeLiteral.get(this.delegate.getClass());
     this.skipFastClassGeneration = skipFastClassGeneration;
+    this.scanner = scanner;
   }
 
   /**
    * Returns a module which creates bindings for provider methods from the given module.
    */
   public static Module forModule(Module module) {
-    return forObject(module, false);
+    return forObject(module, false, PROVIDES_BUILDER);
+  }
+
+  /**
+   * Returns a module which creates bindings methods in the module that match the scanner.
+   */
+  public static Module forModule(Module module, ModuleAnnotatedMethodScanner scanner) {
+    return forObject(module, false, scanner);
   }
 
   /**
@@ -75,18 +101,20 @@
    * are only interested in Module metadata.
    */
   public static Module forObject(Object object) {
-    return forObject(object, true);
+    return forObject(object, true, PROVIDES_BUILDER);
   }
 
-  private static Module forObject(Object object, boolean skipFastClassGeneration) {
+  private static Module forObject(Object object, boolean skipFastClassGeneration,
+      ModuleAnnotatedMethodScanner scanner) {
     // avoid infinite recursion, since installing a module always installs itself
     if (object instanceof ProviderMethodsModule) {
       return Modules.EMPTY_MODULE;
     }
 
-    return new ProviderMethodsModule(object, skipFastClassGeneration);
+    return new ProviderMethodsModule(object, skipFastClassGeneration, scanner);
   }
 
+  @Override
   public synchronized void configure(Binder binder) {
     for (ProviderMethod<?> providerMethod : getProviderMethods(binder)) {
       providerMethod.configure(binder);
@@ -107,8 +135,9 @@
             && !method.isBridge() && !method.isSynthetic()) {
           methodsBySignature.put(new Signature(method), method);
         }
-        if (isProvider(method)) {
-          result.add(createProviderMethod(binder, method));
+        Optional<Annotation> annotation = isProvider(binder, method);
+        if (annotation.isPresent()) {
+          result.add(createProviderMethod(binder, method, annotation.get()));
         }
       }
     }
@@ -125,9 +154,11 @@
         }
         // now we know matching signature is in a subtype of method.getDeclaringClass()
         if (overrides(matchingSignature, method)) {
+          String annotationString = provider.getAnnotation().annotationType() == Provides.class
+              ? "@Provides" : "@" + provider.getAnnotation().annotationType().getCanonicalName();
           binder.addError(
-              "Overriding @Provides methods is not allowed."
-                  + "\n\t@Provides method: %s\n\toverridden by: %s",
+              "Overriding " + annotationString + " methods is not allowed."
+                  + "\n\t" + annotationString + " method: %s\n\toverridden by: %s",
               method,
               matchingSignature);
           break;
@@ -143,10 +174,24 @@
    * Synthetic bridge methods are excluded. Starting with JDK 8, javac copies annotations onto
    * bridge methods (which always have erased signatures).
    */
-  private static boolean isProvider(Method method) {
-    return !method.isBridge()
-        && !method.isSynthetic()
-        && method.isAnnotationPresent(Provides.class);
+  private Optional<Annotation> isProvider(Binder binder, Method method) {
+    if (method.isBridge() || method.isSynthetic()) {
+      return Optional.absent();
+    }
+    Annotation annotation = null;
+    for (Class<? extends Annotation> annotationClass : scanner.annotationClasses()) {
+      Annotation foundAnnotation = method.getAnnotation(annotationClass);
+      if (foundAnnotation != null) {
+        if (annotation != null) {
+          binder.addError("More than one annotation claimed by %s on method %s."
+              + " Methods can only have one annotation claimed per scanner.",
+              scanner, method);
+          return Optional.absent();
+        }
+        annotation = foundAnnotation;
+      }
+    }
+    return Optional.fromNullable(annotation);
   }
 
   private final class Signature {
@@ -197,42 +242,30 @@
     return a.getDeclaringClass().getPackage().equals(b.getDeclaringClass().getPackage());
   }
 
-  private <T> ProviderMethod<T> createProviderMethod(Binder binder, Method method) {
+  private <T> ProviderMethod<T> createProviderMethod(Binder binder, Method method,
+      Annotation annotation) {
     binder = binder.withSource(method);
     Errors errors = new Errors(method);
 
     // prepare the parameter providers
-    List<Dependency<?>> dependencies = Lists.newArrayList();
+    InjectionPoint point = InjectionPoint.forMethod(method, typeLiteral);
+    List<Dependency<?>> dependencies = point.getDependencies();
     List<Provider<?>> parameterProviders = Lists.newArrayList();
-    List<TypeLiteral<?>> parameterTypes = typeLiteral.getParameterTypes(method);
-    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
-    for (int i = 0; i < parameterTypes.size(); i++) {
-      Key<?> key = getKey(errors, parameterTypes.get(i), method, parameterAnnotations[i]);
-      if (key.equals(LOGGER_KEY)) {
-        // If it was a Logger, change the key to be unique & bind it to a
-        // provider that provides a logger with a proper name.
-        // This solves issue 482 (returning a new anonymous logger on every call exhausts memory)
-        Key<Logger> loggerKey = Key.get(Logger.class, UniqueAnnotations.create());
-        binder.bind(loggerKey).toProvider(new LogProvider(method));
-        key = loggerKey;
-      }
-      dependencies.add(Dependency.get(key));
-      parameterProviders.add(binder.getProvider(key));        
+    for (Dependency<?> dependency : point.getDependencies()) {
+      parameterProviders.add(binder.getProvider(dependency));
     }
 
     @SuppressWarnings("unchecked") // Define T as the method's return type.
     TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method);
-
     Key<T> key = getKey(errors, returnType, method, method.getAnnotations());
+    key = scanner.prepareMethod(binder, annotation, key, point);
     Class<? extends Annotation> scopeAnnotation
         = Annotations.findScopeAnnotation(errors, method.getAnnotations());
-
     for (Message message : errors.getMessages()) {
       binder.addError(message);
     }
-
     return ProviderMethod.create(key, method, delegate, ImmutableSet.copyOf(dependencies),
-        parameterProviders, scopeAnnotation, skipFastClassGeneration);
+        parameterProviders, scopeAnnotation, skipFastClassGeneration, annotation);
   }
 
   <T> Key<T> getKey(Errors errors, TypeLiteral<T> type, Member member, Annotation[] annotations) {
@@ -248,17 +281,4 @@
   @Override public int hashCode() {
     return delegate.hashCode();
   }
-  
-  /** A provider that returns a logger based on the method name. */
-  private static final class LogProvider implements Provider<Logger> {
-    private final String name;
-    
-    public LogProvider(Method method) {
-      this.name = method.getDeclaringClass().getName() + "." + method.getName();
-    }
-    
-    public Logger get() {
-      return Logger.getLogger(name);
-    }
-  }
 }
diff --git a/core/src/com/google/inject/spi/Elements.java b/core/src/com/google/inject/spi/Elements.java
index f507fe0..986582e 100644
--- a/core/src/com/google/inject/spi/Elements.java
+++ b/core/src/com/google/inject/spi/Elements.java
@@ -302,7 +302,11 @@
     }
 
     public <T> Provider<T> getProvider(final Key<T> key) {
-      final ProviderLookup<T> element = new ProviderLookup<T>(getElementSource(), key);
+      return getProvider(Dependency.get(key));
+    }
+
+    public <T> Provider<T> getProvider(final Dependency<T> dependency) {
+      final ProviderLookup<T> element = new ProviderLookup<T>(getElementSource(), dependency);
       elements.add(element);
       return element.getProvider();
     }
diff --git a/core/src/com/google/inject/spi/InjectionPoint.java b/core/src/com/google/inject/spi/InjectionPoint.java
index 653f87c..267e0ab 100644
--- a/core/src/com/google/inject/spi/InjectionPoint.java
+++ b/core/src/com/google/inject/spi/InjectionPoint.java
@@ -307,6 +307,20 @@
   }
 
   /**
+   * Returns a new injection point for the specified method of {@code type}.
+   * This is useful for extensions that need to build dependency graphs from
+   * arbitrary methods.
+   *
+   * @param method any single method present on {@code type}.
+   * @param type the concrete type that defines {@code method}.
+   *
+   * @since 4.0
+   */
+  public static <T> InjectionPoint forMethod(Method method, TypeLiteral<T> type) {
+    return new InjectionPoint(type, method, false);
+  }
+
+  /**
    * Returns all static method and field injection points on {@code type}.
    *
    * @return a possibly empty set of injection points. The set has a specified iteration order. All
diff --git a/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java
new file mode 100644
index 0000000..99b6d91
--- /dev/null
+++ b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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 com.google.inject.spi;
+
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.internal.ProviderMethodsModule;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+/**
+ * Allows extensions to scan modules for annotated methods and bind those methods
+ * as providers, similar to {@code @Provides} methods.
+ */
+public abstract class ModuleAnnotatedMethodScanner {
+  
+  /**
+   * Scans the module for methods and returns a module that will bind the methods
+   * that match this scanner.
+   */
+  public final Module forModule(Module module) {
+    return ProviderMethodsModule.forModule(module, this);
+  }
+
+  /**
+   * Returns the annotations this should scan for. Every method in the module that has one of these
+   * annotations will create a Provider binding, with the return value of the binding being what's
+   * provided and the parameters of the method being dependencies of the provider.
+   */
+  public abstract Set<? extends Class<? extends Annotation>> annotationClasses();
+
+  /**
+   * Prepares a method for binding. This {@code key} parameter is the key discovered from looking at
+   * the binding annotation and return value of the method. Implementations can modify the key to
+   * instead bind to another key. For example, Multibinder may want to change
+   * {@code @SetProvides String provideFoo()} to bind into a unique Key within the multibinder
+   * instead of binding {@code String}.
+   *
+   * <p>The injection point and annotation are provided in case the implementation wants to set the
+   * key based on the property of the annotation or if any additional preparation is needed for any
+   * of the dependencies. The annotation is guaranteed to be an instance of one the classes returned
+   * by {@link #annotationClasses}.
+   */
+  public abstract <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
+      InjectionPoint injectionPoint);
+
+}
diff --git a/core/src/com/google/inject/spi/ProviderLookup.java b/core/src/com/google/inject/spi/ProviderLookup.java
index e4b5da6..a232431 100644
--- a/core/src/com/google/inject/spi/ProviderLookup.java
+++ b/core/src/com/google/inject/spi/ProviderLookup.java
@@ -39,12 +39,17 @@
  */
 public final class ProviderLookup<T> implements Element {
   private final Object source;
-  private final Key<T> key;
+  private final Dependency<T> dependency;
   private Provider<T> delegate;
 
   public ProviderLookup(Object source, Key<T> key) {
+    this(source, Dependency.get(checkNotNull(key, "key")));
+  }
+
+  /** @since 4.0 */
+  public ProviderLookup(Object source, Dependency<T> dependency) {
     this.source = checkNotNull(source, "source");
-    this.key = checkNotNull(key, "key");
+    this.dependency = checkNotNull(dependency, "dependency");
   }
 
   public Object getSource() {
@@ -52,7 +57,11 @@
   }
 
   public Key<T> getKey() {
-    return key;
+    return dependency.getKey();
+  }
+
+  public Dependency<T> getDependency() {
+    return dependency;
   }
 
   public <T> T acceptVisitor(ElementVisitor<T> visitor) {
@@ -70,7 +79,7 @@
   }
 
   public void applyTo(Binder binder) {
-    initializeDelegate(binder.withSource(getSource()).getProvider(key));
+    initializeDelegate(binder.withSource(getSource()).getProvider(dependency));
   }
 
   /**
@@ -93,16 +102,16 @@
             "This Provider cannot be used until the Injector has been created.");
         return delegate.get();
       }
-      
+
       public Set<Dependency<?>> getDependencies() {
         // We depend on Provider<T>, not T directly.  This is an important distinction
         // for dependency analysis tools that short-circuit on providers.
-        Key<?> providerKey = key.ofType(Types.providerOf(key.getTypeLiteral().getType()));
+        Key<?> providerKey = getKey().ofType(Types.providerOf(getKey().getTypeLiteral().getType()));
         return ImmutableSet.<Dependency<?>>of(Dependency.get(providerKey));
       }
 
       @Override public String toString() {
-        return "Provider<" + key.getTypeLiteral() + ">";
+        return "Provider<" + getKey().getTypeLiteral() + ">";
       }
     };
   }
diff --git a/core/src/com/google/inject/spi/ProvidesMethodBinding.java b/core/src/com/google/inject/spi/ProvidesMethodBinding.java
index 010e936..a862fcc 100644
--- a/core/src/com/google/inject/spi/ProvidesMethodBinding.java
+++ b/core/src/com/google/inject/spi/ProvidesMethodBinding.java
@@ -19,11 +19,13 @@
 import com.google.inject.Key;
 import com.google.inject.Provides;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 
 /**
- * An {@literal @}{@link Provides} binding.
- * 
+ * An {@literal @}{@link Provides} binding or binding produced by a
+ * {@link ModuleAnnotatedMethodScanner}.
+ *
  * @since 4.0
  * @author sameb@google.com (Sam Berlin)
  */
@@ -37,4 +39,12 @@
   
   /** Returns the key of the binding. */
   Key<T> getKey();
+
+  /**
+   * Returns the annotation that caused this binding to be created. For {@code @Provides} methods,
+   * this is an instance of the {@code @Provides} annotation. For bindings from
+   * {@link ModuleAnnotatedMethodScanner}, this is the annotation that caused the scanner to produce
+   * the binding.
+   */
+  Annotation getAnnotation();
 }
diff --git a/core/test/com/google/inject/AllTests.java b/core/test/com/google/inject/AllTests.java
index e7ca9da..f700608 100644
--- a/core/test/com/google/inject/AllTests.java
+++ b/core/test/com/google/inject/AllTests.java
@@ -31,6 +31,7 @@
 import com.google.inject.spi.HasDependenciesTest;
 import com.google.inject.spi.InjectionPointTest;
 import com.google.inject.spi.InjectorSpiTest;
+import com.google.inject.spi.ModuleAnnotatedMethodScannerTest;
 import com.google.inject.spi.ModuleRewriterTest;
 import com.google.inject.spi.ModuleSourceTest;
 import com.google.inject.spi.ProviderMethodsTest;
@@ -86,6 +87,7 @@
     suite.addTestSuite(MembersInjectorTest.class);
     suite.addTestSuite(ModulesTest.class);
     suite.addTestSuite(ModuleTest.class);
+    suite.addTestSuite(ModuleAnnotatedMethodScannerTest.class);
     suite.addTestSuite(NullableInjectionPointTest.class);
     suite.addTestSuite(OptionalBindingTest.class);
     suite.addTestSuite(OverrideModuleTest.class);
diff --git a/core/test/com/google/inject/ScopesTest.java b/core/test/com/google/inject/ScopesTest.java
index fd7a4eb..59aa596 100644
--- a/core/test/com/google/inject/ScopesTest.java
+++ b/core/test/com/google/inject/ScopesTest.java
@@ -162,6 +162,22 @@
   interface A {}
   static class AImpl implements A {}
 
+  @Retention(RUNTIME)
+  @interface Component {}
+
+  @Component
+  @Singleton
+  interface ComponentAnnotationTest {}
+  static class ComponentAnnotationTestImpl implements ComponentAnnotationTest {}
+
+  public void testScopingAnnotationsOnAbstractTypeIsValidForComponent() {
+    Guice.createInjector(new AbstractModule() {
+      @Override protected void configure() {
+        bind(ComponentAnnotationTest.class).to(ComponentAnnotationTestImpl.class);
+      }
+    });
+  }
+
   public void testScopingAnnotationsOnAbstractTypeViaImplementedBy() {
     try {
       Guice.createInjector().getInstance(D.class);
diff --git a/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java b/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java
new file mode 100644
index 0000000..62f8220
--- /dev/null
+++ b/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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 com.google.inject.spi;
+
+import static com.google.inject.Asserts.assertContains;
+import static com.google.inject.name.Names.named;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Binding;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.internal.util.StackTraceElements;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
+import junit.framework.TestCase;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.Set;
+
+/** Tests for {@link ModuleAnnotatedMethodScanner} usage. */
+public class ModuleAnnotatedMethodScannerTest extends TestCase {
+  
+  public void testScanning() throws Exception {
+    Module module = new AbstractModule() {
+      @Override protected void configure() {
+        install(new NamedMunger().forModule(this));
+      }
+      
+      @TestProvides @Named("foo") String foo() {
+        return "foo";
+      }
+      
+      @TestProvides @Named("foo2") String foo2() {
+        return "foo2";
+      }
+    };
+    Injector injector = Guice.createInjector(module);
+
+    // assert no bindings named "foo" or "foo2" exist -- they were munged.
+    assertNull(injector.getExistingBinding(Key.get(String.class, named("foo"))));
+    assertNull(injector.getExistingBinding(Key.get(String.class, named("foo2"))));
+
+    Binding<String> fooBinding = injector.getBinding(Key.get(String.class, named("foo-munged")));
+    Binding<String> foo2Binding = injector.getBinding(Key.get(String.class, named("foo2-munged")));
+    assertEquals("foo", fooBinding.getProvider().get());
+    assertEquals("foo2", foo2Binding.getProvider().get());
+    
+    // Validate the provider has a sane toString
+    assertEquals(methodName(TestProvides.class, "foo", module),
+        fooBinding.getProvider().toString());
+    assertEquals(methodName(TestProvides.class, "foo2", module),
+        foo2Binding.getProvider().toString());
+  }
+
+  public void testMoreThanOneClaimedAnnotationFails() throws Exception {
+    final NamedMunger scanner = new NamedMunger();
+    Module module = new AbstractModule() {
+      @Override protected void configure() {
+        install(scanner.forModule(this));
+      }
+      
+      @TestProvides @TestProvides2 String foo() {
+        return "foo";
+      }
+    };
+    try {
+      Guice.createInjector(module);
+      fail();
+    } catch(CreationException expected) {
+      assertEquals(1, expected.getErrorMessages().size());
+      assertContains(expected.getMessage(),
+          "More than one annotation claimed by " + scanner + " on method "
+              + module.getClass().getName() + ".foo(). Methods can only have "
+              + "one annotation claimed per scanner.");
+    }
+  }
+  
+  private String methodName(Class<? extends Annotation> annotation, String method, Object container)
+      throws Exception {
+    return "@" + annotation.getName() + " "
+        + StackTraceElements.forMember(container.getClass().getDeclaredMethod(method));
+  }
+  
+  @Documented @Target(METHOD) @Retention(RUNTIME)
+  private @interface TestProvides {}
+
+  @Documented @Target(METHOD) @Retention(RUNTIME)
+  private @interface TestProvides2 {}
+  
+  private static class NamedMunger extends ModuleAnnotatedMethodScanner {
+    @Override
+    public Set<? extends Class<? extends Annotation>> annotationClasses() {
+      return ImmutableSet.of(TestProvides.class, TestProvides2.class);
+    }
+
+    @Override
+    public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
+        InjectionPoint injectionPoint) {
+      return Key.get(key.getTypeLiteral(),
+          Names.named(((Named) key.getAnnotation()).value() + "-munged"));
+    }
+  }
+}
diff --git a/core/test/com/google/inject/spi/ProviderMethodsTest.java b/core/test/com/google/inject/spi/ProviderMethodsTest.java
index cf08e46..5814967 100644
--- a/core/test/com/google/inject/spi/ProviderMethodsTest.java
+++ b/core/test/com/google/inject/spi/ProviderMethodsTest.java
@@ -21,6 +21,8 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.inject.AbstractModule;
 import com.google.inject.Binder;
 import com.google.inject.Binding;
@@ -32,24 +34,33 @@
 import com.google.inject.Key;
 import com.google.inject.Module;
 import com.google.inject.Provides;
+import com.google.inject.ProvisionException;
 import com.google.inject.Singleton;
 import com.google.inject.Stage;
+import com.google.inject.TypeLiteral;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.InternalFlags;
 import com.google.inject.internal.ProviderMethod;
 import com.google.inject.internal.ProviderMethodsModule;
 import com.google.inject.name.Named;
 import com.google.inject.name.Names;
+import com.google.inject.util.Providers;
 import com.google.inject.util.Types;
 
 import junit.framework.TestCase;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 /**
@@ -211,7 +222,7 @@
   public void testGenericProviderMethods() {
     Injector injector = Guice.createInjector(
         new ProvideTs<String>("A", "B") {}, new ProvideTs<Integer>(1, 2) {});
-    
+
     assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("First"))));
     assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Second"))));
     assertEquals(ImmutableSet.of("A", "B"),
@@ -246,7 +257,7 @@
       return ImmutableSet.of(first, second);
     }
   }
-  
+
   public void testAutomaticProviderMethods() {
     Injector injector = Guice.createInjector((Module) new AbstractModule() {
       @Override protected void configure() { }
@@ -281,7 +292,7 @@
     Injector injector = Guice.createInjector(installsSelf);
     assertEquals("A5", injector.getInstance(String.class));
   }
-  
+
   public void testWildcardProviderMethods() {
     final List<String> strings = ImmutableList.of("A", "B", "C");
     final List<Number> numbers = ImmutableList.<Number>of(1, 2, 3);
@@ -312,8 +323,8 @@
     @Inject Class<?> type;
   }
 
-  public void testProviderMethodDependenciesAreExposed() {
-    Injector injector = Guice.createInjector(new AbstractModule() {
+  public void testProviderMethodDependenciesAreExposed() throws Exception {
+    Module module = new AbstractModule() {
       @Override protected void configure() {
         bind(Integer.class).toInstance(50);
         bindConstant().annotatedWith(Names.named("units")).to("Kg");
@@ -321,13 +332,18 @@
       @Provides @Named("weight") String provideWeight(Integer count, @Named("units") String units) {
         return count + units;
       }
-    });
+    };
+    Injector injector = Guice.createInjector(module);
 
     ProviderInstanceBinding<?> binding = (ProviderInstanceBinding<?>) injector.getBinding(
         Key.get(String.class, Names.named("weight")));
-    assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Integer.class)),
-        Dependency.get(Key.get(String.class, Names.named("units")))),
-        binding.getDependencies());
+    Method method =
+      module.getClass().getDeclaredMethod("provideWeight", Integer.class, String.class);
+    InjectionPoint point = new InjectionPoint(TypeLiteral.get(module.getClass()), method, false);
+    assertEquals(ImmutableSet.<Dependency<?>>of(
+        new Dependency<Integer>(point, Key.get(Integer.class), false, 0),
+        new Dependency<String>(point, Key.get(String.class, Names.named("units")), false, 1)),
+         binding.getDependencies());
   }
 
   public void testNonModuleProviderMethods() {
@@ -357,9 +373,9 @@
         element instanceof ProviderInstanceBinding);
 
     ProviderInstanceBinding binding = (ProviderInstanceBinding) element;
-    javax.inject.Provider provider = binding.getUserSuppliedProvider();    
+    javax.inject.Provider provider = binding.getUserSuppliedProvider();
     assertTrue(provider instanceof ProviderMethod);
-    assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());    
+    assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());
     assertSame(provider, binding.getProviderInstance());
   }
 
@@ -372,29 +388,29 @@
       });
       fail();
     } catch (CreationException expected) {
-      assertContains(expected.getMessage(), 
+      assertContains(expected.getMessage(),
           "1) Provider methods must return a value. Do not return void.",
           getClass().getName(), ".provideFoo(ProviderMethodsTest.java:");
     }
   }
-  
+
   public void testInjectsJustOneLogger() {
     AtomicReference<Logger> loggerRef = new AtomicReference<Logger>();
     Injector injector = Guice.createInjector(new FooModule(loggerRef));
-    
+
     assertNull(loggerRef.get());
     injector.getInstance(Integer.class);
     Logger lastLogger = loggerRef.getAndSet(null);
     assertNotNull(lastLogger);
     injector.getInstance(Integer.class);
     assertSame(lastLogger, loggerRef.get());
-    
-    assertEquals(FooModule.class.getName() + ".foo", lastLogger.getName());
+
+    assertEquals(FooModule.class.getName(), lastLogger.getName());
   }
-  
+
   private static class FooModule extends AbstractModule {
     private final AtomicReference<Logger> loggerRef;
-    
+
     public FooModule(AtomicReference<Logger> loggerRef) {
       this.loggerRef = loggerRef;
     }
@@ -407,7 +423,7 @@
       return 42;
     }
   }
-  
+
   public void testSpi() throws Exception {
     Module m1 = new AbstractModule() {
       @Override protected void configure() {}
@@ -418,7 +434,7 @@
       @Provides Integer provideInt(@Named("foo") String dep) { return 42; }
     };
     Injector injector = Guice.createInjector(m1, m2);
-    
+
     Binding<String> stringBinding =
         injector.getBinding(Key.get(String.class, Names.named("foo")));
     ProvidesMethodBinding<String> stringMethod =
@@ -429,7 +445,7 @@
     assertEquals(((HasDependencies) stringBinding).getDependencies(),
         stringMethod.getDependencies());
     assertEquals(Key.get(String.class, Names.named("foo")), stringMethod.getKey());
-    
+
     Binding<Integer> intBinding = injector.getBinding(Integer.class);
     ProvidesMethodBinding<Integer> intMethod =
         intBinding.acceptTargetVisitor(new BindingCapturer<Integer>());
@@ -439,21 +455,21 @@
     assertEquals(((HasDependencies) intBinding).getDependencies(),
         intMethod.getDependencies());
     assertEquals(Key.get(Integer.class), intMethod.getKey());
-    
+
   }
-  
+
   private static class BindingCapturer<T> extends DefaultBindingTargetVisitor<T, ProvidesMethodBinding<T>>
       implements ProvidesMethodTargetVisitor<T, ProvidesMethodBinding<T>> {
-    
+
     @SuppressWarnings("unchecked")
     public ProvidesMethodBinding<T> visit(
         ProvidesMethodBinding<? extends T> providesMethodBinding) {
       return (ProvidesMethodBinding<T>)providesMethodBinding;
     }
-    
+
     @Override protected ProvidesMethodBinding<T> visitOther(Binding<? extends T> binding) {
       throw new IllegalStateException("unexpected visit of: " + binding);
-    }    
+    }
   }
 
   public void testProvidesMethodVisibility() {
@@ -495,7 +511,7 @@
       fail("Expected injector creation failure");
     } catch (CreationException expected) {
       // both of our super class bindings cause errors
-      assertContains(expected.getMessage(), 
+      assertContains(expected.getMessage(),
           "A binding to java.lang.Long was already configured",
           "A binding to java.lang.Integer was already configured");
     }
@@ -561,14 +577,14 @@
   public void testShareFastClassWithSuperClass() {
     CallerInspecterSubClassModule module = new CallerInspecterSubClassModule();
     Guice.createInjector(Stage.PRODUCTION, module);
-    assertEquals("Expected provider methods in the same class to share fastclass classes", 
+    assertEquals("Expected provider methods in the same class to share fastclass classes",
         module.fooCallerClass, module.barCallerClass);
     assertFalse(
         "Did not expect provider methods in the subclasses to share fastclass classes "
             + "with their parent classes",
         module.bazCallerClass.equals(module.barCallerClass));
   }
-  
+
 
   private static class CallerInspecterSubClassModule extends CallerInspecterModule {
     String bazCallerClass;
@@ -594,7 +610,7 @@
     @Provides @Named("unrawlist") List<String> rawParameterProvider(@Named("rawlist") List f) {
       return f;
     }
-    
+
     @Provides @Named("list") List<String> annotatedGenericProviderMethod() {
       return new ArrayList<String>();
     }
@@ -617,7 +633,7 @@
       Guice.createInjector(new SubClassModule());
       fail();
     } catch (CreationException e) {
-      assertContains(e.getMessage(), 
+      assertContains(e.getMessage(),
           "Overriding @Provides methods is not allowed.",
           "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
           "overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -634,7 +650,7 @@
       Guice.createInjector(new SubClassModule());
       fail();
     } catch (CreationException e) {
-      assertContains(e.getMessage(), 
+      assertContains(e.getMessage(),
           "Overriding @Provides methods is not allowed.",
           "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
           "overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -651,7 +667,7 @@
       Guice.createInjector(new SubClassModule());
       fail();
     } catch (CreationException e) {
-      assertContains(e.getMessage(), 
+      assertContains(e.getMessage(),
           "Overriding @Provides methods is not allowed.",
           "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
           "overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -667,13 +683,13 @@
       Guice.createInjector(new SubClassModule());
       fail();
     } catch (CreationException e) {
-      assertContains(e.getMessage(), 
+      assertContains(e.getMessage(),
           "Overriding @Provides methods is not allowed.",
           "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
           "overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
     }
   }
-  
+
 
   public void testOverrideProviderMethod_covariantOverrideDoesntHaveProvides() {
     class SubClassModule extends SuperClassModule {
@@ -685,7 +701,7 @@
       Guice.createInjector(new SubClassModule());
       fail();
     } catch (CreationException e) {
-      assertContains(e.getMessage(), 
+      assertContains(e.getMessage(),
           "Overriding @Provides methods is not allowed.",
           "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
           "overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -702,7 +718,7 @@
       Guice.createInjector(new SubClassModule());
       fail();
     } catch (CreationException e) {
-      assertContains(e.getMessage(), 
+      assertContains(e.getMessage(),
           "Overriding @Provides methods is not allowed.",
           "@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
           "overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -747,11 +763,11 @@
       Guice.createInjector(new SubClassModule());
       fail();
     } catch (CreationException e) {
-      assertContains(e.getMessage(), 
+      assertContains(e.getMessage(),
           "Overriding @Provides methods is not allowed.",
-          "@Provides method: " + SuperClassModule.class.getName() 
+          "@Provides method: " + SuperClassModule.class.getName()
               + ".annotatedGenericParameterProviderMethod()",
-          "overridden by: " + SubClassModule.class.getName() 
+          "overridden by: " + SubClassModule.class.getName()
               + ".annotatedGenericParameterProviderMethod()");
     }
   }
@@ -767,7 +783,7 @@
       Guice.createInjector(new SubClassModule());
       fail();
     } catch (CreationException e) {
-      assertContains(e.getMessage(), 
+      assertContains(e.getMessage(),
           "Overriding @Provides methods is not allowed.",
           "@Provides method: " + SuperClassModule.class.getName() + ".rawProvider()",
           "overridden by: " + SubClassModule.class.getName() + ".rawProvider()");
@@ -836,4 +852,101 @@
   public void testIgnoreSyntheticBridgeMethods() {
     Guice.createInjector(new ModuleImpl());
   }
+
+  public void testNullability() throws Exception {
+    Module module = new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(String.class).toProvider(Providers.<String>of(null));
+      }
+
+      @SuppressWarnings("unused")
+      @Provides
+      Integer fail(String foo) {
+        return 1;
+      }
+
+      @SuppressWarnings("unused")
+      @Provides
+      Long succeed(@Nullable String foo) {
+        return 2L;
+      }
+    };
+    Injector injector = Guice.createInjector(module);
+    InjectionPoint fooPoint = InjectionPoint.forMethod(
+        module.getClass().getDeclaredMethod("fail", String.class),
+        TypeLiteral.get(module.getClass()));
+    Dependency<?> fooDependency = Iterables.getOnlyElement(fooPoint.getDependencies());
+
+    runNullableTest(injector, fooDependency, module);
+
+    injector.getInstance(Long.class);
+  }
+
+  private void runNullableTest(Injector injector, Dependency<?> dependency, Module module) {
+    switch (InternalFlags.getNullableProvidesOption()) {
+      case ERROR:
+        validateNullableFails(injector, module);
+        break;
+      case IGNORE:
+        validateNullableIgnored(injector);
+        break;
+      case WARN:
+        validateNullableWarns(injector, dependency);
+        break;
+    }
+  }
+
+  private void validateNullableFails(Injector injector, Module module) {
+    try {
+      injector.getInstance(Integer.class);
+      fail();
+    } catch (ProvisionException expected) {
+      assertContains(expected.getMessage(),
+          "1) null returned by binding at " + module.getClass().getName() + ".configure(",
+          "but parameter 0 of " + module.getClass().getName() + ".fail() is not @Nullable",
+          "while locating java.lang.String",
+          "for parameter 0 at " + module.getClass().getName() + ".fail(",
+          "while locating java.lang.Integer");
+
+      assertEquals(1, expected.getErrorMessages().size());
+    }
+  }
+
+  private void validateNullableIgnored(Injector injector) {
+    injector.getInstance(Integer.class); // no exception
+  }
+
+  private void validateNullableWarns(Injector injector, Dependency<?> dependency) {
+    final List<LogRecord> logRecords = Lists.newArrayList();
+    final Handler fakeHandler = new Handler() {
+      @Override
+      public void publish(LogRecord logRecord) {
+        logRecords.add(logRecord);
+      }
+      @Override
+      public void flush() {}
+      @Override
+      public void close() throws SecurityException {}
+    };
+    Logger.getLogger(Guice.class.getName()).addHandler(fakeHandler);
+    try {
+      injector.getInstance(Integer.class); // no exception, but assert it does log.
+      LogRecord record = Iterables.getOnlyElement(logRecords);
+      assertEquals(
+          "Guice injected null into parameter {0} of {1} (a {2}), please mark it @Nullable."
+              + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
+              + " error.",
+          record.getMessage());
+      assertEquals(dependency.getParameterIndex(), record.getParameters()[0]);
+      assertEquals(Errors.convert(dependency.getInjectionPoint().getMember()),
+          record.getParameters()[1]);
+      assertEquals(Errors.convert(dependency.getKey()), record.getParameters()[2]);
+    } finally {
+      Logger.getLogger(Guice.class.getName()).removeHandler(fakeHandler);
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface Nullable {}
 }