Added some new error messages:

 - when a method or constructor has a binding annotation, rather than its parameters

 - when a type has a scope annotation, but that scope is not bound

 - when a scope annotation is applied to an abstract type or interface

I suspect these will find lots of bugs!

git-svn-id: https://google-guice.googlecode.com/svn/trunk@523 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/AbstractModule.java b/src/com/google/inject/AbstractModule.java
index 25ff52e..87c6491 100644
--- a/src/com/google/inject/AbstractModule.java
+++ b/src/com/google/inject/AbstractModule.java
@@ -22,6 +22,7 @@
 import com.google.inject.binder.AnnotatedConstantBindingBuilder;
 import com.google.inject.binder.LinkedBindingBuilder;
 import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.Message;
 import com.google.inject.spi.SourceProviders;
 import com.google.inject.spi.TypeConverter;
 import java.lang.annotation.Annotation;
@@ -139,6 +140,13 @@
   }
 
   /**
+   * @see Binder#addError(Message)
+   */
+  protected void addError(Message message) {
+    binder.addError(message);
+  }
+
+  /**
    * @see Binder#requestStaticInjection(Class[])
    */
   protected void requestStaticInjection(Class<?>... types) {
diff --git a/src/com/google/inject/BindCommandProcessor.java b/src/com/google/inject/BindCommandProcessor.java
index f155e13..e44d18a 100644
--- a/src/com/google/inject/BindCommandProcessor.java
+++ b/src/com/google/inject/BindCommandProcessor.java
@@ -23,6 +23,7 @@
 import com.google.inject.commands.BindScoping.Visitor;
 import com.google.inject.commands.BindTarget;
 import com.google.inject.internal.Annotations;
+import com.google.inject.internal.Classes;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.internal.StackTraceElements;
@@ -91,7 +92,7 @@
           return scope;
         } else {
           errors.scopeNotFound(scopeAnnotation);
-          return Scopes.NO_SCOPE;
+          return null;
         }
       }
 
@@ -207,6 +208,15 @@
         errors.at(StackTraceElements.forType(annotationType)).missingBindingAnnotation(source);
       }
     }
+
+    Class<? super T> rawType = key.getRawType();
+    if (!Classes.isConcrete(rawType)) {
+      Class<? extends Annotation> scopeAnnotation = Scopes.getScopeAnnotation(errors, rawType);
+      if (scopeAnnotation != null) {
+        errors.at(StackTraceElements.forType(rawType))
+            .scopeAnnotationOnAbstractType(scopeAnnotation, rawType, source);
+      }
+    }
   }
 
   <T> InvalidBindingImpl<T> invalidBinding(InjectorImpl injector, Key<T> key, Object source) {
diff --git a/src/com/google/inject/Binder.java b/src/com/google/inject/Binder.java
index 2e32780..9382b75 100644
--- a/src/com/google/inject/Binder.java
+++ b/src/com/google/inject/Binder.java
@@ -260,6 +260,11 @@
   void addError(Throwable t);
 
   /**
+   * Records an error message to be presented to the user at a later time.
+   */
+  void addError(Message message);
+
+  /**
    * Returns the provider used to obtain instances for the given injection key.
    * The returned will not be valid until the {@link Injector} has been
    * created. The provider will throw an {@code IllegalStateException} if you
diff --git a/src/com/google/inject/ConstructionProxy.java b/src/com/google/inject/ConstructionProxy.java
index fb5267d..39e3e3c 100644
--- a/src/com/google/inject/ConstructionProxy.java
+++ b/src/com/google/inject/ConstructionProxy.java
@@ -16,8 +16,8 @@
 
 package com.google.inject;
 
+import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Member;
 import java.util.List;
 
 /**
@@ -36,9 +36,8 @@
   List<Parameter<?>> getParameters();
 
   /**
-   * Returns the injected method or constructor. If the injected member is
-   * synthetic (such as generated code for method interception), the natural
-   * constructor is returned.
+   * Returns the injected constructor. If the injected constructor is synthetic (such as generated
+   * code for method interception), the natural constructor is returned.
    */
-  Member getMember();
+  Constructor getConstructor();
 }
diff --git a/src/com/google/inject/ConstructorInjector.java b/src/com/google/inject/ConstructorInjector.java
index 89caef9..66cdada 100644
--- a/src/com/google/inject/ConstructorInjector.java
+++ b/src/com/google/inject/ConstructorInjector.java
@@ -20,6 +20,8 @@
 import com.google.inject.InjectorImpl.SingleParameterInjector;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
+import com.google.inject.internal.StackTraceElements;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.List;
 
@@ -48,10 +50,17 @@
   SingleParameterInjector<?>[] createParameterInjector(Errors errors,
       InjectorImpl injector, ConstructionProxy<T> constructionProxy)
       throws ErrorsException {
-    return constructionProxy.getParameters().isEmpty()
-        ? null // default constructor.
-        : injector.getParametersInjectors(constructionProxy.getMember(),
-            constructionProxy.getParameters(), errors);
+    Constructor constructor = constructionProxy.getConstructor();
+    Object source = StackTraceElements.forMember(constructor);
+    errors.pushSource(source);
+    try {
+      return constructionProxy.getParameters().isEmpty()
+          ? null // default constructor.
+          : injector.getParametersInjectors(constructor,
+              constructionProxy.getParameters(), errors);
+    } finally {
+      errors.popSource(source);
+    }
   }
 
   /**
diff --git a/src/com/google/inject/DefaultConstructionProxyFactory.java b/src/com/google/inject/DefaultConstructionProxyFactory.java
index c99f508..33bfdc8 100644
--- a/src/com/google/inject/DefaultConstructionProxyFactory.java
+++ b/src/com/google/inject/DefaultConstructionProxyFactory.java
@@ -21,7 +21,6 @@
 import com.google.inject.internal.GuiceFastClass;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Member;
 import java.lang.reflect.Modifier;
 import java.util.List;
 import net.sf.cglib.reflect.FastClass;
@@ -60,7 +59,7 @@
         public List<Parameter<?>> getParameters() {
           return parameters;
         }
-        public Member getMember() {
+        public Constructor getConstructor() {
           return constructor;
         }
       };
@@ -78,7 +77,7 @@
       public List<Parameter<?>> getParameters() {
         return parameters;
       }
-      public Member getMember() {
+      public Constructor getConstructor() {
         return constructor;
       }
     };
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index 2bf0a63..d6966ba 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -488,7 +488,13 @@
     }
 
     if (scope == null) {
-      scope = Scopes.getScopeForType(errors, type, scopes);
+      Class<? extends Annotation> scopeAnnotation = Scopes.getScopeAnnotation(errors, type);
+      if (scopeAnnotation != null) {
+        scope = scopes.get(scopeAnnotation);
+        if (scope == null) {
+          errors.at(StackTraceElements.forType(type)).scopeNotFound(scopeAnnotation);
+        }
+      }
     }
 
     Key<T> key = Key.get(type);
@@ -830,8 +836,14 @@
    * @param member to which the parameters belong
    * @return injections
    */
-  SingleParameterInjector<?>[] getParametersInjectors(
-      Member member, List<Parameter<?>> parameters, Errors errors) throws ErrorsException {
+  <M extends Member & AnnotatedElement> SingleParameterInjector<?>[] getParametersInjectors(
+      M member, List<Parameter<?>> parameters, Errors errors) throws ErrorsException {
+    Annotation misplacedBindingAnnotation
+        = Keys.findBindingAnnotation(errors, member, member.getAnnotations());
+    if (misplacedBindingAnnotation != null) {
+      errors.misplacedBindingAnnotation(member, misplacedBindingAnnotation);
+    }
+
     SingleParameterInjector<?>[] parameterInjectors
         = new SingleParameterInjector<?>[parameters.size()];
     int index = 0;
diff --git a/src/com/google/inject/ProviderMethods.java b/src/com/google/inject/ProviderMethods.java
index 86408a8..258f81d 100644
--- a/src/com/google/inject/ProviderMethods.java
+++ b/src/com/google/inject/ProviderMethods.java
@@ -18,12 +18,11 @@
 
 import com.google.common.collect.Lists;
 import com.google.inject.internal.Errors;
+import com.google.inject.internal.Keys;
 import com.google.inject.internal.StackTraceElements;
-import com.google.inject.spi.SourceProvider;
-import com.google.inject.spi.SourceProviders;
+import com.google.inject.spi.Message;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.util.List;
@@ -47,24 +46,12 @@
 
     final Object providers;
 
-    Object source;
-
-    final SourceProvider sourceProvider = new SourceProvider() {
-      public Object source() {
-        return source;
-      }
-    };
-
     ProviderMethodsModule(Object providers) {
       this.providers = providers;
     }
 
     protected void configure() {
-      SourceProviders.withDefault(sourceProvider, new Runnable() {
-        public void run() {
-          bindProviderMethods(providers.getClass());
-        }
-      });
+      bindProviderMethods(providers.getClass());
     }
 
     void bindProviderMethods(Class<?> clazz) {
@@ -82,21 +69,25 @@
     }
 
     <T> void bindProviderMethod(final Method method) {
-      this.source = StackTraceElements.forMember(method);
+      Errors errors = new Errors()
+          .pushSource(StackTraceElements.forMember(method));
 
       method.setAccessible(true);
 
       Class<? extends Annotation> scopeAnnotation
-          = findScopeAnnotation(method.getAnnotations());
-      Annotation bindingAnnotation = findBindingAnnotation(method, method.getAnnotations());
+          = findScopeAnnotation(errors, method.getAnnotations());
+      Annotation bindingAnnotation
+          = Keys.findBindingAnnotation(errors, method, method.getAnnotations());
 
-      final List<Provider<?>> parameterProviders
-          = findParameterProviders(method);
+      final List<Provider<?>> parameterProviders = findParameterProviders(errors, method);
+
+      for (Message message : errors.getMessages()) {
+        addError(message);
+      }
 
       // Define T as the method's return type.
       @SuppressWarnings("unchecked")
-      TypeLiteral<T> returnType 
-          = (TypeLiteral<T>) TypeLiteral.get(method.getGenericReturnType());
+      TypeLiteral<T> returnType = (TypeLiteral<T>) TypeLiteral.get(method.getGenericReturnType());
 
       Provider<T> provider = new Provider<T>() {
         public T get() {
@@ -107,8 +98,8 @@
 
           try {
             // We know this cast is safe becase T is the method's return type.
-            @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})
-            T result = (T) method.invoke(providers, parameters);
+            @SuppressWarnings({ "unchecked", "UnnecessaryLocalVariable" }) T result = (T) method
+                .invoke(providers, parameters);
             return result;
           }
           catch (IllegalAccessException e) {
@@ -134,13 +125,14 @@
       }
     }
 
-    List<Provider<?>> findParameterProviders(Method method) {
+    List<Provider<?>> findParameterProviders(Errors errors, Method method) {
       List<Provider<?>> parameterProviders = Lists.newArrayList();
 
       Type[] parameterTypes = method.getGenericParameterTypes();
       Annotation[][] parameterAnnotations = method.getParameterAnnotations();
       for (int i = 0; i < parameterTypes.length; i++) {
-        Annotation bindingAnnotation = findBindingAnnotation(method, parameterAnnotations[i]);
+        Annotation bindingAnnotation
+            = Keys.findBindingAnnotation(errors, method, parameterAnnotations[i]);
         Key<?> key = bindingAnnotation == null ? Key.get(parameterTypes[i])
             : Key.get(parameterTypes[i], bindingAnnotation);
         Provider<?> provider = getProvider(key);
@@ -150,15 +142,17 @@
       return parameterProviders;
     }
 
-    Class<? extends Annotation> findScopeAnnotation(Annotation[] annotations) {
+    /**
+     * Returns the scoping annotation, or null if there isn't one.
+     */
+    Class<? extends Annotation> findScopeAnnotation(Errors errors, Annotation[] annotations) {
       Class<? extends Annotation> found = null;
 
       for (Annotation annotation : annotations) {
         if (annotation.annotationType()
             .isAnnotationPresent(ScopeAnnotation.class)) {
           if (found != null) {
-            addError(new Errors().duplicateScopeAnnotations(
-                found, annotation.annotationType()).toString());
+            errors.duplicateScopeAnnotations(found, annotation.annotationType());
           } else {
             found = annotation.annotationType();
           }
@@ -167,23 +161,5 @@
 
       return found;
     }
-
-    Annotation findBindingAnnotation(Member member, Annotation[] annotations) {
-      Annotation found = null;
-
-      for (Annotation annotation : annotations) {
-        if (annotation.annotationType()
-            .isAnnotationPresent(BindingAnnotation.class)) {
-          if (found != null) {
-            addError(new Errors().duplicateBindingAnnotations(member,
-                found.annotationType(), annotation.annotationType()).toString());
-          } else {
-            found = annotation;
-          }
-        }
-      }
-
-      return found;
-    }
   }
 }
diff --git a/src/com/google/inject/ProxyFactory.java b/src/com/google/inject/ProxyFactory.java
index 6923798..07aca7c 100644
--- a/src/com/google/inject/ProxyFactory.java
+++ b/src/com/google/inject/ProxyFactory.java
@@ -25,7 +25,6 @@
 import com.google.inject.internal.ReferenceCache;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
@@ -188,7 +187,7 @@
         return parameters;
       }
 
-      public Member getMember() {
+      public Constructor getConstructor() {
         return standardConstructor;
       }
     };
diff --git a/src/com/google/inject/Scopes.java b/src/com/google/inject/Scopes.java
index c124b81..f0bc525 100644
--- a/src/com/google/inject/Scopes.java
+++ b/src/com/google/inject/Scopes.java
@@ -18,7 +18,6 @@
 
 import com.google.inject.internal.Errors;
 import java.lang.annotation.Annotation;
-import java.util.Map;
 
 /**
  * Built in scope implementations.
@@ -88,27 +87,22 @@
   };
 
   /**
-   * Gets the scope for a type based on its annotations. Returns {@code null}
-   * if none specified.
-   *
-   * @param implementation type
-   * @param scopes map of scope names to scopes
+   * Returns the scope annotation on {@code type}, or null if none is specified.
    */
-  static Scope getScopeForType(Errors errors, Class<?> implementation,
-      Map<Class<? extends Annotation>, Scope> scopes) {
+  static Class<? extends Annotation> getScopeAnnotation(
+      Errors errors, Class<?> implementation) {
     Class<? extends Annotation> found = null;
     for (Annotation annotation : implementation.getAnnotations()) {
       if (isScopeAnnotation(annotation)) {
         if (found != null) {
           errors.duplicateScopeAnnotations(found, annotation.annotationType());
-        }
-        else {
+        } else {
           found = annotation.annotationType();
         }
       }
     }
 
-    return scopes.get(found);
+    return found;
   }
 
   static boolean isScopeAnnotation(Annotation annotation) {
diff --git a/src/com/google/inject/commands/CommandRecorder.java b/src/com/google/inject/commands/CommandRecorder.java
index a4b3307..c9c17f0 100644
--- a/src/com/google/inject/commands/CommandRecorder.java
+++ b/src/com/google/inject/commands/CommandRecorder.java
@@ -16,18 +16,29 @@
 
 package com.google.inject.commands;
 
-import com.google.inject.*;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.Stage;
+import com.google.inject.TypeLiteral;
 import com.google.inject.binder.AnnotatedBindingBuilder;
 import com.google.inject.binder.AnnotatedConstantBindingBuilder;
 import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.Message;
 import com.google.inject.spi.SourceProviders;
 import static com.google.inject.spi.SourceProviders.defaultSource;
 import com.google.inject.spi.TypeConverter;
-import org.aopalliance.intercept.MethodInterceptor;
-
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.aopalliance.intercept.MethodInterceptor;
 
 /**
  * Records commands executed by a module so they can be inspected or
@@ -78,8 +89,8 @@
   }
 
   private class RecordingBinder implements Binder {
-    private final Set<Module> modules = new HashSet<Module>();
-    private final List<Command> commands = new ArrayList<Command>();
+    private final Set<Module> modules = Sets.newHashSet();
+    private final List<Command> commands = Lists.newArrayList();
 
     public void bindInterceptor(
         Matcher<? super Class<?>> classMatcher,
@@ -115,6 +126,10 @@
       commands.add(new AddThrowableErrorCommand(defaultSource(), t));
     }
 
+    public void addError(Message message) {
+      throw new UnsupportedOperationException("TODO");
+    }
+
     public <T> BindCommand<T>.BindingBuilder bind(Key<T> key) {
       BindCommand<T> bindCommand = new BindCommand<T>(defaultSource(), key);
       commands.add(bindCommand);
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index ec97b23..5e82f4a 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -175,11 +175,13 @@
   }
 
   public Errors missingBindingAnnotation(Object source) {
-    return addMessage("Please annotate with @BindingAnnotation. Bound at %s.", source);
+    return addMessage("Please annotate with @BindingAnnotation.%n"
+        + " Bound at %s.", source);
   }
 
   public Errors missingRuntimeRetention(Object source) {
-    return addMessage("Please annotate with @Retention(RUNTIME). Bound at %s.", source);
+    return addMessage("Please annotate with @Retention(RUNTIME).%n"
+        + " Bound at %s.", source);
   }
 
   public Errors missingScopeAnnotation() {
@@ -204,6 +206,17 @@
     return addMessage("No scope is bound to %s.", scopeAnnotation);
   }
 
+  public Errors scopeAnnotationOnAbstractType(
+      Class<? extends Annotation> scopeAnnotation, Class<?> type, Object source) {
+    return addMessage("%s is annotated with %s, but scope annotations are not supported "
+        + "for abstract types.%n Bound at %s.", type, scopeAnnotation, source);
+  }
+
+  public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) {
+    return addMessage("%s is annotated with %s, but binding annotations should be applied "
+        + "to its parameters instead.", member, bindingAnnotation);
+  }
+
   private static final String CONSTRUCTOR_RULES =
       "Classes must have either one (and only one) constructor "
           + "annotated with @Inject or a zero-argument constructor.";
@@ -465,6 +478,11 @@
           return c.getName();
         }
       },
+      new Converter<Member>(Member.class) {
+        public String toString(Member member) {
+          return MoreTypes.canonicalize(member).toString();
+        }
+      },
       new Converter<Key>(Key.class) {
         public String toString(Key k) {
           StringBuilder result = new StringBuilder();
diff --git a/src/com/google/inject/internal/Keys.java b/src/com/google/inject/internal/Keys.java
index 7f58ede..0b2beac 100644
--- a/src/com/google/inject/internal/Keys.java
+++ b/src/com/google/inject/internal/Keys.java
@@ -30,17 +30,29 @@
    */
   public static Key<?> get(Type type, Member member, Annotation[] annotations, Errors errors)
       throws ErrorsException {
+    Annotation found = findBindingAnnotation(errors, member, annotations);
+    errors.throwIfNecessary();
+    return found == null ? Key.get(type) : Key.get(type, found);
+  }
+
+  /**
+   * Returns the binding annotation on {@code member}, or null if there isn't one.
+   */
+  public static Annotation findBindingAnnotation(
+      Errors errors, Member member, Annotation[] annotations) {
     Annotation found = null;
+
     for (Annotation annotation : annotations) {
-      if (annotation.annotationType().getAnnotation(BindingAnnotation.class) != null) {
-        if (found == null) {
-          found = annotation;
+      if (annotation.annotationType().isAnnotationPresent(BindingAnnotation.class)) {
+        if (found != null) {
+          errors.duplicateBindingAnnotations(member,
+              found.annotationType(), annotation.annotationType());
         } else {
-          throw errors.duplicateBindingAnnotations(
-              member, found.annotationType(), annotation.annotationType()).toException();
+          found = annotation;
         }
       }
     }
-    return found == null ? Key.get(type) : Key.get(type, found);
+
+    return found;
   }
 }
diff --git a/src/com/google/inject/spi/SourceProviders.java b/src/com/google/inject/spi/SourceProviders.java
index e0e5784..1f566c4 100644
--- a/src/com/google/inject/spi/SourceProviders.java
+++ b/src/com/google/inject/spi/SourceProviders.java
@@ -86,8 +86,7 @@
    * Sets the default source provider, runs the given command, and then
    * restores the previous default source provider.
    */
-  public static void withDefault(
-      SourceProvider sourceProvider, Runnable r) {
+  private static void withDefault(SourceProvider sourceProvider, Runnable r) {
     // We use a holder so we perform only 1 thread local access instead of 3.
     SourceProvider[] holder = localSourceProvider.get();
     SourceProvider previous = holder[0];
diff --git a/test/com/google/inject/BinderTest.java b/test/com/google/inject/BinderTest.java
index 7dcd208..2680c73 100644
--- a/test/com/google/inject/BinderTest.java
+++ b/test/com/google/inject/BinderTest.java
@@ -19,8 +19,12 @@
 import static com.google.inject.Asserts.assertContains;
 import static com.google.inject.Asserts.assertNotSerializable;
 import com.google.inject.name.Names;
+import com.google.inject.util.Providers;
 import java.io.IOException;
-import java.util.ArrayList;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
@@ -31,6 +35,23 @@
  */
 public class BinderTest extends TestCase {
 
+  private ParameterizedType parameterizedWithVariable;
+  private ParameterizedType parameterizedWithWildcard;
+  private TypeVariable typeVariable;
+  private WildcardType wildcardType;
+
+  <T> void parameterizedWithVariable(List<T> typeWithVariables) {}
+  <T> void parameterizedWithWildcard(List<? extends Comparable> typeWithWildcard) {}
+
+  @Override protected void setUp() throws Exception {
+    parameterizedWithVariable = (ParameterizedType) getClass()
+        .getDeclaredMethod("parameterizedWithVariable", List.class).getGenericParameterTypes()[0];
+    parameterizedWithWildcard = (ParameterizedType) getClass()
+        .getDeclaredMethod("parameterizedWithWildcard", List.class).getGenericParameterTypes()[0];
+    typeVariable = (TypeVariable) parameterizedWithVariable.getActualTypeArguments()[0];
+    wildcardType = (WildcardType) parameterizedWithWildcard.getActualTypeArguments()[0];
+  }
+
   Provider<Foo> fooProvider;
 
   public void testProviderFromBinder() {
@@ -188,19 +209,29 @@
   }
 
   /** Test for issue 186 */
-  public void testGuiceRefusesToCreateParameterizedClasses() {
-    try {
-      Guice.createInjector(new AbstractModule() {
-        protected void configure() {
-          bind(List.class).to(ArrayList.class);
-        }
-      });
-      fail();
-    } catch (CreationException expected) {
-      Asserts.assertContains(expected.getMessage(),
-          "Cannot instantiate Parameterized class java.util.List");
-    }
+  public void testBindDisallowedTypes() throws NoSuchMethodException {
+    Type[] types = new Type[] {
+        parameterizedWithVariable,
+        parameterizedWithWildcard,
+        typeVariable,
+        wildcardType,
+    };
 
+    for (Type type : types) {
+      @SuppressWarnings("unchecked") final
+      Key<Object> key = (Key<Object>) Key.get(type);
+
+      try {
+        Guice.createInjector(new AbstractModule() {
+          protected void configure() {
+            bind(key).toProvider(Providers.of(null));
+          }
+        });
+        fail("Guice should not allow bindings to " + type);
+      } catch (CreationException e) {
+        assertContains(e.getMessage(), "Cannot bind types that have type variables");
+      }
+    }
   }
 
 //  public void testBindInterfaceWithoutImplementation() {
diff --git a/test/com/google/inject/ErrorMessagesTest.java b/test/com/google/inject/ErrorMessagesTest.java
index 6c415db..880846a 100644
--- a/test/com/google/inject/ErrorMessagesTest.java
+++ b/test/com/google/inject/ErrorMessagesTest.java
@@ -3,7 +3,6 @@
 package com.google.inject;
 
 import static com.google.inject.Asserts.assertContains;
-import com.google.inject.util.Providers;
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
@@ -11,11 +10,6 @@
 import java.lang.annotation.Retention;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import java.lang.annotation.Target;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
-import java.lang.reflect.WildcardType;
-import java.util.List;
 import junit.framework.TestCase;
 
 /**
@@ -25,23 +19,6 @@
  */
 public class ErrorMessagesTest extends TestCase {
 
-  private ParameterizedType parameterizedWithVariable;
-  private ParameterizedType parameterizedWithWildcard;
-  private TypeVariable typeVariable;
-  private WildcardType wildcardType;
-
-  <T> void parameterizedWithVariable(List<T> typeWithVariables) {}
-  <T> void parameterizedWithWildcard(List<? extends Comparable> typeWithWildcard) {}
-
-  @Override protected void setUp() throws Exception {
-    parameterizedWithVariable = (ParameterizedType) getClass()
-        .getDeclaredMethod("parameterizedWithVariable", List.class).getGenericParameterTypes()[0];
-    parameterizedWithWildcard = (ParameterizedType) getClass()
-        .getDeclaredMethod("parameterizedWithWildcard", List.class).getGenericParameterTypes()[0];
-    typeVariable = (TypeVariable) parameterizedWithVariable.getActualTypeArguments()[0];
-    wildcardType = (WildcardType) parameterizedWithWildcard.getActualTypeArguments()[0];
-  }
-
   private class InnerClass {}
 
   public void testInjectInnerClass() throws Exception {
@@ -67,45 +44,6 @@
     }
   }
 
-  public void testBindDisallowedTypes() throws NoSuchMethodException {
-    Type[] types = new Type[] {
-        parameterizedWithVariable,
-        parameterizedWithWildcard,
-        typeVariable,
-        wildcardType,
-    };
-
-    for (Type type : types) {
-      @SuppressWarnings("unchecked") final
-      Key<Object> key = (Key<Object>) Key.get(type);
-
-      try {
-        Guice.createInjector(new AbstractModule() {
-          protected void configure() {
-            bind(key).toProvider(Providers.of(null));
-          }
-        });
-        fail("Guice should not allow bindings to " + type);
-      } catch (CreationException e) {
-        assertContains(e.getMessage(), "Cannot bind types that have type variables");
-      }
-    }
-  }
-
-  public void testScopingAnnotationsOnAbstractTypes() {
-    try {
-      Guice.createInjector(new AbstractModule() {
-        protected void configure() {
-          bind(A.class).to(AImpl.class);
-        }
-      });
-      fail();
-    } catch (CreationException expected) {
-      assertContains(expected.getMessage(),
-          "Scope annotations on abstract types are not supported.");
-    }
-  }
-
   /** Demonstrates issue 64, when setAccessible() fails. */
   public void testGetUninjectableClass() {
     try {
@@ -122,18 +60,15 @@
     }
   }
 
-  @Singleton
-  interface A {}
-  class AImpl implements A {}
-
   public void testBindingAnnotationsOnMethodsAndConstructors() {
     try {
       Guice.createInjector().getInstance(B.class);
       fail();
     } catch (ProvisionException expected) {
       assertContains(expected.getMessage(),
-          "Binding annotations on injected methods are not supported. "
-              + "Annotate the parameter instead?");
+          "method " + B.class.getName() + ".injectMe() ",
+          "is annotated with @", Green.class.getName() + "(), ",
+          "but binding annotations should be applied to its parameters instead.");
     }
 
     try {
@@ -141,8 +76,9 @@
       fail();
     } catch (ProvisionException expected) {
       assertContains(expected.getMessage(),
-          "Binding annotations on injected constructors are not supported. "
-              + "Annotate the parameter instead?");
+          "constructor " + C.class.getName() + "() ",
+          "is annotated with @", Green.class.getName() + "(), ",
+          "but binding annotations should be applied to its parameters instead.");
     }
   }
 
diff --git a/test/com/google/inject/ScopesTest.java b/test/com/google/inject/ScopesTest.java
index eaa5921..d364104 100644
--- a/test/com/google/inject/ScopesTest.java
+++ b/test/com/google/inject/ScopesTest.java
@@ -16,13 +16,14 @@
 
 package com.google.inject;
 
+import com.google.common.collect.Maps;
+import static com.google.inject.Asserts.assertContains;
 import java.io.IOException;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import java.lang.annotation.Target;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import junit.framework.TestCase;
@@ -106,6 +107,49 @@
         injector.getInstance(AnnotatedSingleton.class));
   }
 
+  public void testScopingAnnotationsOnAbstractTypes() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        protected void configure() {
+          bind(A.class).to(AImpl.class);
+        }
+      });
+      fail();
+    } catch (CreationException expected) {
+      assertContains(expected.getMessage(),
+          "Error at " + A.class.getName() + ".class(ScopesTest.java:",
+          A.class.getName() + " is annotated with " + Singleton.class.getName(),
+          "but scope annotations are not supported for abstract types.");
+    }
+  }
+
+  @Singleton
+  interface A {}
+  static class AImpl implements A {}
+  
+  public void testScopeUsedButNotBound() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        protected void configure() {
+          bind(B.class).in(CustomScoped.class);
+          bind(C.class);
+        }
+      });
+      fail();
+    } catch (CreationException expected) {
+      assertContains(expected.getMessage(),
+          "1) Error at " + getClass().getName(), ".configure(ScopesTest.java:",
+          "No scope is bound to " + CustomScoped.class.getName(),
+          "2) Error at " + C.class.getName() + ".class",
+          "No scope is bound to " + CustomScoped.class.getName());
+    }
+  }
+
+  static class B {}
+
+  @CustomScoped
+  static class C {}
+
   public void testSingletonsInProductionStage() {
     Guice.createInjector(Stage.PRODUCTION, singletonsModule);
 
@@ -155,7 +199,7 @@
   }
 
   class RememberProviderScope implements Scope {
-    final Map<Key<?>, Provider<?>> providers = new HashMap<Key<?>, Provider<?>>();
+    final Map<Key<?>, Provider<?>> providers = Maps.newHashMap();
     public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
       providers.put(key, unscoped);
       return unscoped;