Hopefully the last of the big exceptions refactorings. I went through all of the places we're adding context to our Errors object and made sure we're never doubling-up -- specifing the same injection point or key multiple times.

The new errors have nice 'at' lines for parameters, fields and linked bindings. Hopefully this makes it easier to follow the stacktraces. Additional context (such as the binding's origin in a module) could be added later if desired. Currently we have module-specific sources in CreationExceptions, and plain old binding sources elsewhere. The end result is messages that can look like this:

com.google.inject.ProvisionException: Guice provision errors:

1) Error injecting constructor, java.lang.UnsupportedOperationException
  at com.google.inject.ProvisionExceptionTest$RealD.<init>(ProvisionExceptionTest.java:284)
  at binding for com.google.inject.ProvisionExceptionTest$RealD.class(ProvisionExceptionTest.java:284)
  at binding for com.google.inject.ProvisionExceptionTest$D.class(ProvisionExceptionTest.java:1)

1 error

git-svn-id: https://google-guice.googlecode.com/svn/trunk@652 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/BindingProcessor.java b/src/com/google/inject/BindingProcessor.java
index 78ad9f2..d141ae3 100644
--- a/src/com/google/inject/BindingProcessor.java
+++ b/src/com/google/inject/BindingProcessor.java
@@ -162,10 +162,9 @@
 
         FactoryProxy<T> factory = new FactoryProxy<T>(key, targetKey, source);
         creationListeners.add(factory);
-        InternalFactory<? extends T> scopedFactory
-            = Scopes.scope(key, injector, factory, scope);
+        InternalFactory<? extends T> scopedFactory = Scopes.scope(key, injector, factory, scope);
         putBinding(new LinkedBindingImpl<T>(
-                injector, key, source, scopedFactory, scope, targetKey, loadStrategy));
+            injector, key, source, scopedFactory, scope, targetKey, loadStrategy));
         return null;
       }
 
diff --git a/src/com/google/inject/BoundProviderFactory.java b/src/com/google/inject/BoundProviderFactory.java
index 397aeeb..705dd4e 100644
--- a/src/com/google/inject/BoundProviderFactory.java
+++ b/src/com/google/inject/BoundProviderFactory.java
@@ -20,8 +20,6 @@
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.spi.Dependency;
-import com.google.inject.spi.Message;
-import java.util.Collection;
 
 /**
  * Delegates to a custom factory which is also bound in the injector.
@@ -49,11 +47,12 @@
 
   public T get(Errors errors, InternalContext context, Dependency<?> dependency)
       throws ErrorsException {
+    errors = errors.withSource(providerKey);
     Provider<? extends T> provider = providerFactory.get(errors, context, dependency);
     try {
       return errors.checkForNull(provider.get(), source, dependency);
     } catch(RuntimeException userException) {
-      throw errors.withSource(source).errorInProvider(userException).toException();
+      throw errors.errorInProvider(userException).toException();
     }
   }
 
diff --git a/src/com/google/inject/ConstructionProxyFactory.java b/src/com/google/inject/ConstructionProxyFactory.java
index 792645d..af404ef 100644
--- a/src/com/google/inject/ConstructionProxyFactory.java
+++ b/src/com/google/inject/ConstructionProxyFactory.java
@@ -16,8 +16,6 @@
 
 package com.google.inject;
 
-import com.google.inject.internal.Errors;
-import com.google.inject.internal.ErrorsException;
 import com.google.inject.spi.InjectionPoint;
 
 /**
@@ -32,6 +30,5 @@
    *
    * @param injectionPoint an injection point whose member is a constructor of {@code T}.
    */
-  <T> ConstructionProxy<T> get(Errors errors, InjectionPoint injectionPoint)
-      throws ErrorsException;
+  <T> ConstructionProxy<T> get(InjectionPoint injectionPoint);
 }
diff --git a/src/com/google/inject/ConstructorInjector.java b/src/com/google/inject/ConstructorInjector.java
index de36367..eb5233b 100644
--- a/src/com/google/inject/ConstructorInjector.java
+++ b/src/com/google/inject/ConstructorInjector.java
@@ -16,48 +16,48 @@
 
 package com.google.inject;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.inject.InjectorImpl.SingleMemberInjector;
-import com.google.inject.InjectorImpl.SingleParameterInjector;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.spi.InjectionPoint;
 import java.lang.reflect.InvocationTargetException;
-import java.util.List;
 
 /**
- * Injects constructors.
+ * Creates instances using an injectable constructor. After construction, all injectable fields and
+ * methods are injected.
  *
  * @author crazybob@google.com (Bob Lee)
  */
 class ConstructorInjector<T> {
 
   final Class<T> implementation;
-  final SingleMemberInjector[] memberInjectors;
-  final SingleParameterInjector<?>[] parameterInjectors;
+  final InjectionPoint injectionPoint;
+  final ImmutableList<SingleMemberInjector> memberInjectors;
+  final ImmutableList<SingleParameterInjector<?>> parameterInjectors;
   final ConstructionProxy<T> constructionProxy;
 
   ConstructorInjector(Errors errors, InjectorImpl injector, Class<T> implementation)
       throws ErrorsException {
     this.implementation = implementation;
-    constructionProxy = injector.reflection.getConstructionProxy(errors, implementation);
-    parameterInjectors = createParameterInjector(errors, injector, constructionProxy);
-    List<SingleMemberInjector> memberInjectorsList = injector.injectors.get(implementation, errors);
-    memberInjectors = memberInjectorsList.toArray(
-        new SingleMemberInjector[memberInjectorsList.size()]);
-  }
 
-  SingleParameterInjector<?>[] createParameterInjector(Errors errors,
-      InjectorImpl injector, ConstructionProxy<T> constructionProxy)
-      throws ErrorsException {
-    return injector.getParametersInjectors(constructionProxy.getInjectionPoint(), errors);
+    try {
+      this.injectionPoint = InjectionPoint.forConstructorOf(implementation);
+    } catch (ConfigurationException e) {
+      throw errors.merge(e.getErrorMessages()).toException();
+    }
+
+    constructionProxy = injector.constructionProxyFactory.get(injectionPoint);
+    parameterInjectors = injector.getParametersInjectors(injectionPoint.getDependencies(), errors);
+    memberInjectors = injector.injectors.get(implementation, errors);
   }
 
   ImmutableSet<InjectionPoint> getInjectionPoints() {
-    InjectionPoint[] injectionPoints = new InjectionPoint[memberInjectors.length + 1];
+    InjectionPoint[] injectionPoints = new InjectionPoint[memberInjectors.size() + 1];
     injectionPoints[0] = constructionProxy.getInjectionPoint();
-    for (int i = 0; i < memberInjectors.length; i++) {
-      injectionPoints[i+1] = memberInjectors[i].getInjectionPoint();
+    int i = 1;
+    for (SingleMemberInjector memberInjector : memberInjectors) {
+      injectionPoints[i++] = memberInjector.getInjectionPoint();
     }
     return ImmutableSet.of(injectionPoints);
   }
@@ -87,11 +87,10 @@
       // First time through...
       constructionContext.startConstruction();
       try {
-        Object[] parameters = InjectorImpl.getParameters(errors, context, parameterInjectors);
+        Object[] parameters = SingleParameterInjector.getAll(errors, context, parameterInjectors);
         t = constructionProxy.newInstance(parameters);
         constructionContext.setProxyDelegates(t);
-      }
-      finally {
+      } finally {
         constructionContext.finishConstruction();
       }
 
@@ -100,7 +99,7 @@
       constructionContext.setCurrentReference(t);
 
       // Inject fields and methods.
-      for (InjectorImpl.SingleMemberInjector injector : memberInjectors) {
+      for (SingleMemberInjector injector : memberInjectors) {
         injector.inject(errors, context, t);
       }
 
diff --git a/src/com/google/inject/DefaultConstructionProxyFactory.java b/src/com/google/inject/DefaultConstructionProxyFactory.java
index eaadba9..20099cf 100644
--- a/src/com/google/inject/DefaultConstructionProxyFactory.java
+++ b/src/com/google/inject/DefaultConstructionProxyFactory.java
@@ -18,7 +18,6 @@
 
 import com.google.inject.internal.BytecodeGen.Visibility;
 import static com.google.inject.internal.BytecodeGen.newFastClass;
-import com.google.inject.internal.Errors;
 import com.google.inject.spi.InjectionPoint;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -35,7 +34,7 @@
  */
 class DefaultConstructionProxyFactory implements ConstructionProxyFactory {
 
-  public <T> ConstructionProxy<T> get(Errors errors, final InjectionPoint injectionPoint) {
+  public <T> ConstructionProxy<T> get(final InjectionPoint injectionPoint) {
     @SuppressWarnings("unchecked") // the injection point is for a constructor of T
     final Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember();
 
@@ -43,14 +42,13 @@
     if (!Modifier.isPublic(constructor.getModifiers())) {
       constructor.setAccessible(true);
       return new ConstructionProxy<T>() {
-        public T newInstance(Object... arguments) throws
-            InvocationTargetException {
+        public T newInstance(Object... arguments) throws InvocationTargetException {
           try {
             return constructor.newInstance(arguments);
           } catch (InstantiationException e) {
-            throw new RuntimeException(e);
+            throw new AssertionError(e); // shouldn't happen, we know this is a concrete type
           } catch (IllegalAccessException e) {
-            throw new AssertionError(e);
+            throw new AssertionError(e); // a security manager is blocking us, we're hosed
           }
         }
         public InjectionPoint getInjectionPoint() {
diff --git a/src/com/google/inject/FactoryProxy.java b/src/com/google/inject/FactoryProxy.java
index c5135c6..fef74ea 100644
--- a/src/com/google/inject/FactoryProxy.java
+++ b/src/com/google/inject/FactoryProxy.java
@@ -50,7 +50,7 @@
 
   public T get(Errors errors, InternalContext context, Dependency<?> dependency)
       throws ErrorsException {
-    return targetFactory.get(errors, context, dependency);
+    return targetFactory.get(errors.withSource(targetKey), context, dependency);
   }
 
   @Override public String toString() {
diff --git a/src/com/google/inject/Initializer.java b/src/com/google/inject/Initializer.java
index 13242a9..7003bff 100644
--- a/src/com/google/inject/Initializer.java
+++ b/src/com/google/inject/Initializer.java
@@ -22,7 +22,7 @@
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.spi.InjectionPoint;
-import com.google.inject.InjectorImpl.SingleMemberInjector;
+import com.google.inject.SingleMemberInjector;
 import java.util.Map;
 import java.util.Set;
 import java.util.List;
diff --git a/src/com/google/inject/InjectionRequestProcessor.java b/src/com/google/inject/InjectionRequestProcessor.java
index 4c71367..39858cb 100644
--- a/src/com/google/inject/InjectionRequestProcessor.java
+++ b/src/com/google/inject/InjectionRequestProcessor.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
-import com.google.inject.InjectorImpl.SingleMemberInjector;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.spi.InjectionPoint;
diff --git a/src/com/google/inject/InjectorBuilder.java b/src/com/google/inject/InjectorBuilder.java
index 7c67b2f..cf47351 100644
--- a/src/com/google/inject/InjectorBuilder.java
+++ b/src/com/google/inject/InjectorBuilder.java
@@ -20,7 +20,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.inject.Reflection.Factory;
 import static com.google.inject.Scopes.SINGLETON;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
@@ -48,7 +47,6 @@
 
   private InjectorImpl parent = null;
   private Stage stage;
-  private Factory reflectionFactory = new RuntimeReflectionFactory();
   private final List<Module> modules = Lists.newLinkedList();
 
   private InjectorImpl injector;
@@ -68,11 +66,6 @@
     return this;
   }
 
-  InjectorBuilder usingReflectionFactory(Factory reflectionFactory) {
-    this.reflectionFactory = reflectionFactory;
-    return this;
-  }
-
   InjectorBuilder parentInjector(InjectorImpl parent) {
     this.parent = parent;
     return this;
@@ -128,8 +121,7 @@
     InterceptorBindingProcessor interceptorCommandProcessor
         = new InterceptorBindingProcessor(errors, injector.state);
     interceptorCommandProcessor.processCommands(elements);
-    ConstructionProxyFactory proxyFactory = interceptorCommandProcessor.createProxyFactory();
-    injector.reflection = reflectionFactory.create(proxyFactory);
+    injector.constructionProxyFactory = interceptorCommandProcessor.createProxyFactory();
     stopwatch.resetAndLog("Interceptors creation");
 
     new ScopeBindingProcessor(errors, injector.state).processCommands(elements);
@@ -197,17 +189,16 @@
           || binding.getLoadStrategy() == LoadStrategy.EAGER) {
         try {
           injector.callInContext(new ContextualCallable<Void>() {
+            Dependency<?> dependency = Dependency.get(binding.key);
             public Void call(InternalContext context) {
-              Dependency<?> dependency = Dependency.get(binding.key);
               context.setDependency(dependency);
-              errors.pushSource(dependency);
+              Errors errorsForBinding = errors.withSource(dependency);
               try {
-                binding.internalFactory.get(errors, context, dependency);
+                binding.internalFactory.get(errorsForBinding, context, dependency);
               } catch (ErrorsException e) {
-                errors.merge(e.getErrors());
+                errorsForBinding.merge(e.getErrors());
               } finally {
                 context.setDependency(null);
-                errors.popSource(dependency);
               }
 
               return null;
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index 6261c0d..c1691da 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -24,8 +24,6 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimaps;
 import com.google.inject.internal.Annotations;
-import com.google.inject.internal.BytecodeGen.Visibility;
-import static com.google.inject.internal.BytecodeGen.newFastClass;
 import com.google.inject.internal.Classes;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
@@ -42,15 +40,12 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Member;
-import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import net.sf.cglib.reflect.FastClass;
-import net.sf.cglib.reflect.FastMethod;
 
 /**
  * Default {@link Injector} implementation.
@@ -63,7 +58,7 @@
   final InjectorImpl parent;
   final BindingsMultimap bindingsMultimap = new BindingsMultimap();
   final Initializer initializer = new Initializer(this);
-  Reflection reflection;
+  ConstructionProxyFactory constructionProxyFactory;
 
   InjectorImpl(@Nullable InjectorImpl parent) {
     this.parent = parent;
@@ -97,10 +92,10 @@
 
   /** Returns the binding for {@code key} */
   public <T> BindingImpl<T> getBinding(Key<T> key) {
-    Errors errors = new Errors(key.getRawType());
+    Errors errors = new Errors(key);
     try {
       BindingImpl<T> result = getBindingOrThrow(key, errors);
-      errors.throwConfigurationExceptionIfNecessary();
+      errors.throwConfigurationExceptionIfErrorsExist();
       return result;
     } catch (ErrorsException e) {
       throw new ConfigurationException(errors.merge(e.getErrors()).getMessages());
@@ -209,7 +204,6 @@
   }
 
   static class ProviderBindingImpl<T> extends BindingImpl<Provider<T>> {
-
     final BindingImpl<T> providedBinding;
 
     ProviderBindingImpl(
@@ -284,11 +278,9 @@
       }
 
       return new ConvertedConstantBindingImpl<T>(this, key, converted, stringBinding);
-    }
-    catch (ErrorsException e) {
+    } catch (ErrorsException e) {
       throw e;
-    }
-    catch (Exception e) {
+    } catch (Exception e) {
       throw errors.conversionError(stringValue, source, type, matchingConverter, e)
           .toException();
     }
@@ -359,7 +351,7 @@
       LoadStrategy loadStrategy, Errors errors) throws ErrorsException {
     // Don't try to inject arrays, or enums.
     if (type.isArray() || type.isEnum()) {
-      throw errors.missingImplementation(type).toException();
+      throw errors.missingImplementation(key).toException();
     }
 
     // Handle @ImplementedBy
@@ -444,28 +436,28 @@
       throw errors.recursiveProviderType().toException();
     }
 
-    // TODO: Make sure the provided type extends type. We at least check the type at runtime below.
-
     // Assume the provider provides an appropriate type. We double check at runtime.
     @SuppressWarnings("unchecked")
-    Key<? extends Provider<T>> providerKey = (Key<? extends Provider<T>>) Key.get(providerType);
+    final Key<? extends Provider<T>> providerKey
+        = (Key<? extends Provider<T>>) Key.get(providerType);
     final BindingImpl<? extends Provider<?>> providerBinding
         = getBindingOrThrow(providerKey, errors);
 
     InternalFactory<T> internalFactory = new InternalFactory<T>() {
       public T get(Errors errors, InternalContext context, Dependency dependency)
           throws ErrorsException {
+        errors = errors.withSource(providerKey);
         Provider<?> provider = providerBinding.internalFactory.get(errors, context, dependency);
         try {
           Object o = provider.get();
           if (o != null && !type.isInstance(o)) {
-            throw errors.withSource(type).subtypeNotProvided(providerType, type).toException();
+            throw errors.subtypeNotProvided(providerType, type).toException();
           }
           @SuppressWarnings("unchecked") // protected by isInstance() check above
           T t = (T) o;
           return t;
         } catch (RuntimeException e) {
-          throw errors.withSource(type).errorInProvider(e).toException();
+          throw errors.errorInProvider(e).toException();
         }
       }
     };
@@ -497,17 +489,17 @@
       throw errors.notASubtype(implementationType, type).toException();
     }
 
-    // After the preceding check, this cast is safe.
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings("unchecked") // After the preceding check, this cast is safe.
     Class<? extends T> subclass = (Class<? extends T>) implementationType;
 
     // Look up the target binding.
-    final BindingImpl<? extends T> targetBinding = getBindingOrThrow(Key.get(subclass), errors);
+    final Key<? extends T> targetKey = Key.get(subclass);
+    final BindingImpl<? extends T> targetBinding = getBindingOrThrow(targetKey, errors);
 
     InternalFactory<T> internalFactory = new InternalFactory<T>() {
       public T get(Errors errors, InternalContext context, Dependency<?> dependency)
           throws ErrorsException {
-        return targetBinding.internalFactory.get(errors, context, dependency);
+        return targetBinding.internalFactory.get(errors.withSource(targetKey), context, dependency);
       }
     };
 
@@ -517,8 +509,7 @@
         key,
         type,
         Scopes.<T>scope(key, this, internalFactory, scope),
-        scope,
-        Key.get(subclass),
+        scope, targetKey,
         loadStrategy);
   }
 
@@ -585,9 +576,9 @@
   }
 
   /** Cached field and method injectors for a type. */
-  final FailableCache<Class<?>, List<SingleMemberInjector>> injectors
-      = new FailableCache<Class<?>, List<SingleMemberInjector>>() {
-    protected List<SingleMemberInjector> create(Class<?> type, Errors errors)
+  final FailableCache<Class<?>, ImmutableList<SingleMemberInjector>> injectors
+      = new FailableCache<Class<?>, ImmutableList<SingleMemberInjector>>() {
+    protected ImmutableList<SingleMemberInjector> create(Class<?> type, Errors errors)
         throws ErrorsException {
       List<InjectionPoint> injectionPoints = Lists.newArrayList();
       try {
@@ -609,10 +600,10 @@
       try {
         Errors errorsForMember = injectionPoint.isOptional()
             ? new Errors(injectionPoint)
-            : errors;
+            : errors.withSource(injectionPoint);
         SingleMemberInjector injector = injectionPoint.getMember() instanceof Field
-            ? new SingleFieldInjector(errorsForMember, this, injectionPoint)
-            : new SingleMethodInjector(errorsForMember, this, injectionPoint);
+            ? new SingleFieldInjector(this, injectionPoint, errorsForMember)
+            : new SingleMethodInjector(this, injectionPoint, errorsForMember);
         injectors.add(injector);
       } catch (ErrorsException ignoredForNow) {
       }
@@ -644,152 +635,35 @@
     }
   }
 
-  class SingleFieldInjector implements SingleMemberInjector {
-    final Field field;
-    final InternalFactory<?> factory;
-    final InjectionPoint injectionPoint;
-    final Dependency<?> dependency;
-
-    public SingleFieldInjector(Errors errors, InjectorImpl injector, InjectionPoint injectionPoint)
-        throws ErrorsException {
-      this.injectionPoint = injectionPoint;
-      this.field = (Field) injectionPoint.getMember();
-      this.dependency = injectionPoint.getDependencies().get(0);
-
-      // Ewwwww...
-      field.setAccessible(true);
-      factory = injector.getInternalFactory(dependency.getKey(), errors.withSource(dependency));
-    }
-
-    public InjectionPoint getInjectionPoint() {
-      return injectionPoint;
-    }
-
-    public void inject(Errors errors, InternalContext context, Object o) {
-      context.setDependency(dependency);
-      errors.pushSource(dependency);
-      try {
-        Object value = factory.get(errors, context, dependency);
-        field.set(o, value);
-      }
-      catch (IllegalAccessException e) {
-        throw new AssertionError(e);
-      }
-      catch (ErrorsException e) {
-        errors.merge(e.getErrors());
-      }
-      finally {
-        context.setDependency(null);
-        errors.popSource(dependency);
-      }
-    }
-  }
-
   /**
    * Returns parameter injectors, or {@code null} if there are no parameters.
    */
-  SingleParameterInjector<?>[] getParametersInjectors(InjectionPoint injectionPoint, Errors errors)
-      throws ErrorsException {
-    errors.pushSource(injectionPoint);
-    try {
-      Member member = injectionPoint.getMember();
-      Annotation misplacedBindingAnnotation = Annotations.findBindingAnnotation(
-          errors, member, ((AnnotatedElement) member).getAnnotations());
-      if (misplacedBindingAnnotation != null) {
-        errors.misplacedBindingAnnotation(member, misplacedBindingAnnotation);
-      }
-    } finally {
-      errors.popSource(injectionPoint);
-    }
-
-    List<Dependency<?>> parameters = injectionPoint.getDependencies();
+  ImmutableList<SingleParameterInjector<?>> getParametersInjectors(
+      List<Dependency<?>> parameters, Errors errors) throws ErrorsException {
     if (parameters.isEmpty()) {
       return null;
     }
 
-    SingleParameterInjector<?>[] parameterInjectors
-        = new SingleParameterInjector<?>[parameters.size()];
-    int index = 0;
+    SingleParameterInjector<?>[] result = new SingleParameterInjector<?>[parameters.size()];
+    int i = 0;
     for (Dependency<?> parameter : parameters) {
-      errors.pushSource(parameter);
       try {
-        parameterInjectors[index] = createParameterInjector(parameter, errors);
+        result[i++] = createParameterInjector(parameter, errors.withSource(parameter));
       } catch (ErrorsException rethrownBelow) {
         // rethrown below
-      } finally {
-        errors.popSource(parameter);
       }
-      index++;
     }
 
     errors.throwIfNecessary();
-    return parameterInjectors;
+    return ImmutableList.of(result);
   }
 
   <T> SingleParameterInjector<T> createParameterInjector(final Dependency<T> dependency,
       final Errors errors) throws ErrorsException {
-    InternalFactory<? extends T> factory
-        = getInternalFactory(dependency.getKey(), errors.withSource(dependency));
+    InternalFactory<? extends T> factory = getInternalFactory(dependency.getKey(), errors);
     return new SingleParameterInjector<T>(dependency, factory);
   }
 
-  static class SingleMethodInjector implements SingleMemberInjector {
-    final MethodInvoker methodInvoker;
-    final SingleParameterInjector<?>[] parameterInjectors;
-    final InjectionPoint injectionPoint;
-
-    public SingleMethodInjector(Errors errors, InjectorImpl injector, InjectionPoint injectionPoint)
-        throws ErrorsException {
-      this.injectionPoint = injectionPoint;
-      final Method method = (Method) injectionPoint.getMember();
-
-      // We can't use FastMethod if the method is private.
-      if (Modifier.isPrivate(method.getModifiers())
-          || Modifier.isProtected(method.getModifiers())) {
-        method.setAccessible(true);
-        methodInvoker = new MethodInvoker() {
-          public Object invoke(Object target, Object... parameters)
-              throws IllegalAccessException, InvocationTargetException {
-            return method.invoke(target, parameters);
-          }
-        };
-      } else {
-        FastClass fastClass = newFastClass(method.getDeclaringClass(),
-            Visibility.forMember(method));
-        final FastMethod fastMethod = fastClass.getMethod(method);
-
-        methodInvoker = new MethodInvoker() {
-          public Object invoke(Object target, Object... parameters)
-              throws IllegalAccessException, InvocationTargetException {
-            return fastMethod.invoke(target, parameters);
-          }
-        };
-      }
-
-      parameterInjectors = injector.getParametersInjectors(injectionPoint, errors);
-    }
-
-    public void inject(Errors errors, InternalContext context, Object o) {
-      try {
-        Object[] parameters = getParameters(errors, context, parameterInjectors);
-        methodInvoker.invoke(o, parameters);
-      } catch (IllegalAccessException e) {
-        throw new AssertionError(e);
-      } catch (InvocationTargetException userException) {
-        Throwable cause = userException.getCause() != null
-            ? userException.getCause()
-            : userException;
-        errors.withSource(injectionPoint).errorInjectingMethod(cause);
-      } catch (ErrorsException e) {
-        errors.merge(e.getErrors());
-      }
-    }
-
-    public InjectionPoint getInjectionPoint() {
-      return injectionPoint;
-    }
-  }
-
   /** Invokes a method. */
   interface MethodInvoker {
     Object invoke(Object target, Object... parameters)
@@ -800,54 +674,11 @@
   final FailableCache<Class<?>, ConstructorInjector<?>> constructors
       = new FailableCache<Class<?>, ConstructorInjector<?>>() {
     @SuppressWarnings("unchecked")
-    protected ConstructorInjector<?> create(Class<?> key, Errors errors) throws ErrorsException {
-      return new ConstructorInjector(errors, InjectorImpl.this, key);
+    protected ConstructorInjector<?> create(Class<?> type, Errors errors) throws ErrorsException {
+      return new ConstructorInjector(errors, InjectorImpl.this, type);
     }
   };
 
-  static class SingleParameterInjector<T> {
-    final Dependency<T> dependency;
-    final InternalFactory<? extends T> factory;
-
-    public SingleParameterInjector(Dependency<T> dependency,
-        InternalFactory<? extends T> factory) {
-      this.dependency = dependency;
-      this.factory = factory;
-    }
-
-    T inject(Errors errors, InternalContext context) throws ErrorsException {
-      context.setDependency(dependency);
-      errors.pushSource(dependency);
-      try {
-        return factory.get(errors, context, dependency);
-      }
-      finally {
-        errors.popSource(dependency);
-        context.setDependency(null);
-      }
-    }
-  }
-
-  /** Iterates over parameter injectors and creates an array of parameter values. */
-  static Object[] getParameters(Errors errors, InternalContext context,
-      SingleParameterInjector[] parameterInjectors) throws ErrorsException {
-    if (parameterInjectors == null) {
-      return null;
-    }
-
-    Object[] parameters = new Object[parameterInjectors.length];
-    for (int i = 0; i < parameters.length; i++) {
-      try {
-        parameters[i] = parameterInjectors[i].inject(errors, context);
-      } catch (ErrorsException e) {
-        errors.merge(e.getErrors());
-      }
-    }
-
-    errors.throwIfNecessary();
-    return parameters;
-  }
-
   void injectMembers(Errors errors, Object o, InternalContext context,
       List<SingleMemberInjector> injectors)
       throws ErrorsException {
@@ -858,7 +689,7 @@
 
   // Not test-covered
   public void injectMembers(final Object o) {
-    Errors errors = new Errors();
+    Errors errors = new Errors(o.getClass());
 
     // configuration/validation stuff throws ConfigurationException
     List<SingleMemberInjector> injectors;
@@ -899,21 +730,19 @@
 
   <T> Provider<T> getProviderOrThrow(final Key<T> key, Errors errors) throws ErrorsException {
     final InternalFactory<? extends T> factory = getInternalFactory(key, errors);
+    final Dependency<T> dependency = Dependency.get(key);
 
     return new Provider<T>() {
       public T get() {
-        final Errors errors = new Errors();
+        final Errors errors = new Errors(dependency);
         try {
           T t = callInContext(new ContextualCallable<T>() {
             public T call(InternalContext context) throws ErrorsException {
-              Dependency<T> dependency = Dependency.get(key);
               context.setDependency(dependency);
-              errors.pushSource(dependency);
               try {
                 return factory.get(errors, context, dependency);
               } finally {
                 context.setDependency(null);
-                errors.popSource(dependency);
               }
             }
           });
@@ -931,7 +760,7 @@
   }
 
   public <T> Provider<T> getProvider(final Key<T> key) {
-    Errors errors = new Errors(key.getRawType());
+    Errors errors = new Errors(key);
     try {
       Provider<T> result = getProviderOrThrow(key, errors);
       errors.throwIfNecessary();
@@ -969,12 +798,6 @@
     }
   }
 
-  /** Injects a field or method in a given object. */
-  public interface SingleMemberInjector {
-    void inject(Errors errors, InternalContext context, Object o);
-    InjectionPoint getInjectionPoint();
-  }
-
   public String toString() {
     return new ToStringBuilder(Injector.class)
         .add("bindings", state.getExplicitBindingsThisLevel())
diff --git a/src/com/google/inject/InternalFactoryToProviderAdapter.java b/src/com/google/inject/InternalFactoryToProviderAdapter.java
index df0b594..a218c1a 100644
--- a/src/com/google/inject/InternalFactoryToProviderAdapter.java
+++ b/src/com/google/inject/InternalFactoryToProviderAdapter.java
@@ -21,8 +21,6 @@
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.internal.SourceProvider;
 import com.google.inject.spi.Dependency;
-import com.google.inject.spi.Message;
-import java.util.Collection;
 
 /**
  * @author crazybob@google.com (Bob Lee)
diff --git a/src/com/google/inject/ProxyFactory.java b/src/com/google/inject/ProxyFactory.java
index eef3aef..153fcc7 100644
--- a/src/com/google/inject/ProxyFactory.java
+++ b/src/com/google/inject/ProxyFactory.java
@@ -21,9 +21,7 @@
 import com.google.inject.internal.BytecodeGen.Visibility;
 import static com.google.inject.internal.BytecodeGen.newEnhancer;
 import static com.google.inject.internal.BytecodeGen.newFastClass;
-import com.google.inject.internal.Errors;
-import com.google.inject.internal.ErrorsException;
-import com.google.inject.internal.FailableCache;
+import com.google.inject.internal.ReferenceCache;
 import com.google.inject.spi.InjectionPoint;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -55,21 +53,19 @@
   }
 
   @SuppressWarnings("unchecked") // the constructed T is the same as the injection point's T
-  public <T> ConstructionProxy<T> get(Errors errors, InjectionPoint injectionPoint)
-      throws ErrorsException {
-    return (ConstructionProxy<T>) constructionProxies.get(injectionPoint, errors);
+  public <T> ConstructionProxy<T> get(InjectionPoint injectionPoint) {
+    return (ConstructionProxy<T>) constructionProxies.get(injectionPoint);
   }
 
   /** Cached construction proxies for each injection point */
-  FailableCache<InjectionPoint, ConstructionProxy> constructionProxies
-      = new FailableCache<InjectionPoint, ConstructionProxy>() {
-    protected ConstructionProxy create(InjectionPoint key, Errors errors) throws ErrorsException {
-      return createConstructionProxy(errors, key);
+  ReferenceCache<InjectionPoint, ConstructionProxy> constructionProxies
+      = new ReferenceCache<InjectionPoint, ConstructionProxy>() {
+    protected ConstructionProxy create(InjectionPoint key) {
+      return createConstructionProxy(key);
     }
   };
 
-  <T> ConstructionProxy<T> createConstructionProxy(Errors errors, InjectionPoint injectionPoint)
-      throws ErrorsException {
+  <T> ConstructionProxy<T> createConstructionProxy(InjectionPoint injectionPoint) {
     @SuppressWarnings("unchecked") // the member of injectionPoint is always a Constructor<T>
     Constructor<T> constructor = (Constructor<T>) injectionPoint.getMember();
     Class<T> declaringClass = constructor.getDeclaringClass();
@@ -82,7 +78,7 @@
       }
     }
     if (applicableAspects.isEmpty()) {
-      return defaultFactory.get(errors, injectionPoint);
+      return defaultFactory.get(injectionPoint);
     }
 
     // Get list of methods from cglib.
@@ -115,21 +111,20 @@
     }
     if (!anyMatched) {
       // not test-covered
-      return defaultFactory.get(errors, injectionPoint);
+      return defaultFactory.get(injectionPoint);
     }
 
     // Create callbacks.
     Callback[] callbacks = new Callback[methods.size()];
 
-    @SuppressWarnings("unchecked") Class<? extends Callback>[] callbackTypes = new Class[methods
-        .size()];
+    @SuppressWarnings("unchecked")
+    Class<? extends Callback>[] callbackTypes = new Class[methods.size()];
     for (int i = 0; i < methods.size(); i++) {
       MethodInterceptorsPair pair = methodInterceptorsPairs.get(i);
       if (!pair.hasInterceptors()) {
         callbacks[i] = NoOp.INSTANCE;
         callbackTypes[i] = NoOp.class;
-      }
-      else {
+      } else {
         callbacks[i] = new InterceptorStackCallback(pair.method, pair.interceptors);
         callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class;
       }
@@ -156,7 +151,7 @@
    * Creates a construction proxy given a class and parameter types.
    */
   private <T> ConstructionProxy<T> createConstructionProxy(final Class<?> clazz,
-      final InjectionPoint injectionPoint) throws ErrorsException {
+      final InjectionPoint injectionPoint) {
     @SuppressWarnings("unchecked") // injection point's member must be a Constructor<T>
     final Constructor<T> standardConstructor = (Constructor<T>) injectionPoint.getMember();
     FastClass fastClass = newFastClass(clazz, Visibility.PUBLIC);
diff --git a/src/com/google/inject/Reflection.java b/src/com/google/inject/Reflection.java
index f0f14c7..71f9931 100644
--- a/src/com/google/inject/Reflection.java
+++ b/src/com/google/inject/Reflection.java
@@ -17,22 +17,34 @@
 
 package com.google.inject;
 
-import com.google.inject.internal.Errors;
-import com.google.inject.internal.ErrorsException;
+import java.lang.reflect.Constructor;
 
 /**
- * Abstraction for Java's reflection APIs. This interface exists to provide a
- * single place where runtime reflection can be substituted for another
- * mechanism such as CGLib or compile-time code generation.
+ * Abstraction for Java's reflection APIs. This interface exists to provide a single place where
+ * runtime reflection can be substituted for another mechanism such as CGLib or compile-time code
+ * generation.
  *
  * @author jessewilson@google.com (Jesse Wilson)
  */
-interface Reflection {
+class Reflection {
 
-  public <T> ConstructionProxy<T> getConstructionProxy(Errors errors, Class<T> implementation)
-      throws ErrorsException;
+  /**
+   * A placeholder. This enables us to continue processing and gather more
+   * errors but blows up if you actually try to use it.
+   */
+  static class InvalidConstructor {
+    InvalidConstructor() {
+      throw new AssertionError();
+    }
+  }
 
-  interface Factory {
-    Reflection create(ConstructionProxyFactory constructionProxyFactory);
+  @SuppressWarnings("unchecked")
+  static <T> Constructor<T> invalidConstructor() {
+    try {
+      return (Constructor<T>) InvalidConstructor.class.getDeclaredConstructor();
+    }
+    catch (NoSuchMethodException e) {
+      throw new AssertionError(e);
+    }
   }
 }
diff --git a/src/com/google/inject/RuntimeReflectionFactory.java b/src/com/google/inject/RuntimeReflectionFactory.java
deleted file mode 100644
index 0bca520..0000000
--- a/src/com/google/inject/RuntimeReflectionFactory.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Copyright (C) 2008 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;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.inject.internal.Errors;
-import com.google.inject.internal.ErrorsException;
-import com.google.inject.spi.InjectionPoint;
-import java.lang.reflect.Constructor;
-
-/**
- * @author jessewilson@google.com (Jesse Wilson)
- */
-class RuntimeReflectionFactory implements Reflection.Factory {
-  public Reflection create(ConstructionProxyFactory constructionProxyFactory) {
-    return new RuntimeReflection(constructionProxyFactory);
-  }
-
-  private static class RuntimeReflection implements Reflection {
-    private final ConstructionProxyFactory constructionProxyFactory;
-
-    private RuntimeReflection(ConstructionProxyFactory constructionProxyFactory) {
-      this.constructionProxyFactory
-          = checkNotNull(constructionProxyFactory, "constructionProxyFatory");
-    }
-
-    public <T> ConstructionProxy<T> getConstructionProxy(Errors errors, Class<T> implementation)
-        throws ErrorsException {
-      InjectionPoint injectionPoint;
-      try {
-        injectionPoint = InjectionPoint.forConstructorOf(implementation);
-      } catch (ConfigurationException e) {
-        throw errors.merge(e.getErrorMessages()).toException();
-      }
-
-      errors = errors.withSource(injectionPoint);
-      return constructionProxyFactory.get(errors, injectionPoint);
-    }
-
-    /**
-     * A placeholder. This enables us to continue processing and gather more
-     * errors but blows up if you actually try to use it.
-     */
-    static class InvalidConstructor {
-      InvalidConstructor() {
-        throw new AssertionError();
-      }
-    }
-
-    @SuppressWarnings("unchecked")
-    static <T> Constructor<T> invalidConstructor() {
-      try {
-        return (Constructor<T>) InvalidConstructor.class.getDeclaredConstructor();
-      }
-      catch (NoSuchMethodException e) {
-        throw new AssertionError(e);
-      }
-    }
-  }
-}
diff --git a/src/com/google/inject/SingleFieldInjector.java b/src/com/google/inject/SingleFieldInjector.java
new file mode 100644
index 0000000..cff31fb
--- /dev/null
+++ b/src/com/google/inject/SingleFieldInjector.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (C) 2008 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;
+
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.InjectionPoint;
+import java.lang.reflect.Field;
+
+/**
+ * Sets an injectable field.
+ */
+class SingleFieldInjector implements SingleMemberInjector {
+  final Field field;
+  final InjectionPoint injectionPoint;
+  final Dependency<?> dependency;
+  final InternalFactory<?> factory;
+
+  public SingleFieldInjector(InjectorImpl injector, InjectionPoint injectionPoint, Errors errors)
+      throws ErrorsException {
+    this.injectionPoint = injectionPoint;
+    this.field = (Field) injectionPoint.getMember();
+    this.dependency = injectionPoint.getDependencies().get(0);
+
+    // Ewwwww...
+    field.setAccessible(true);
+    factory = injector.getInternalFactory(dependency.getKey(), errors);
+  }
+
+  public InjectionPoint getInjectionPoint() {
+    return injectionPoint;
+  }
+
+  public void inject(Errors errors, InternalContext context, Object o) {
+    errors = errors.withSource(dependency);
+
+    context.setDependency(dependency);
+    try {
+      Object value = factory.get(errors, context, dependency);
+      field.set(o, value);
+    } catch (ErrorsException e) {
+      errors.withSource(injectionPoint).merge(e.getErrors());
+    } catch (IllegalAccessException e) {
+      throw new AssertionError(e); // a security manager is blocking us, we're hosed
+    } finally {
+      context.setDependency(null);
+    }
+  }
+}
diff --git a/src/com/google/inject/SingleMemberInjector.java b/src/com/google/inject/SingleMemberInjector.java
new file mode 100644
index 0000000..7b19845
--- /dev/null
+++ b/src/com/google/inject/SingleMemberInjector.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2008 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;
+
+import com.google.inject.internal.Errors;
+import com.google.inject.spi.InjectionPoint;
+
+/**
+ * Injects a field or method of a given object.
+ */
+public interface SingleMemberInjector {
+  void inject(Errors errors, InternalContext context, Object o);
+  InjectionPoint getInjectionPoint();
+}
diff --git a/src/com/google/inject/SingleMethodInjector.java b/src/com/google/inject/SingleMethodInjector.java
new file mode 100644
index 0000000..56c402e
--- /dev/null
+++ b/src/com/google/inject/SingleMethodInjector.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (C) 2008 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;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.InjectorImpl.MethodInvoker;
+import com.google.inject.internal.BytecodeGen;
+import com.google.inject.internal.BytecodeGen.Visibility;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import com.google.inject.spi.InjectionPoint;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import net.sf.cglib.reflect.FastClass;
+import net.sf.cglib.reflect.FastMethod;
+
+/**
+ * Invokes an injectable method.
+ */
+class SingleMethodInjector implements SingleMemberInjector {
+  final MethodInvoker methodInvoker;
+  final ImmutableList<SingleParameterInjector<?>> parameterInjectors;
+  final InjectionPoint injectionPoint;
+
+  public SingleMethodInjector(InjectorImpl injector, InjectionPoint injectionPoint, Errors errors)
+      throws ErrorsException {
+    this.injectionPoint = injectionPoint;
+    final Method method = (Method) injectionPoint.getMember();
+
+    // We can't use FastMethod if the method is private.
+    int modifiers = method.getModifiers();
+    if (Modifier.isPrivate(modifiers) || Modifier.isProtected(modifiers)) {
+      method.setAccessible(true);
+      methodInvoker = new MethodInvoker() {
+        public Object invoke(Object target, Object... parameters)
+            throws IllegalAccessException, InvocationTargetException {
+          return method.invoke(target, parameters);
+        }
+      };
+    } else {
+      FastClass fastClass = BytecodeGen.newFastClass(
+          method.getDeclaringClass(), Visibility.forMember(method));
+      final FastMethod fastMethod = fastClass.getMethod(method);
+
+      methodInvoker = new MethodInvoker() {
+        public Object invoke(Object target, Object... parameters)
+            throws IllegalAccessException, InvocationTargetException {
+          return fastMethod.invoke(target, parameters);
+        }
+      };
+    }
+
+    parameterInjectors = injector.getParametersInjectors(injectionPoint.getDependencies(), errors);
+  }
+
+  public InjectionPoint getInjectionPoint() {
+    return injectionPoint;
+  }
+
+  public void inject(Errors errors, InternalContext context, Object o) {
+    Object[] parameters;
+    try {
+      parameters = SingleParameterInjector.getAll(errors, context, parameterInjectors);
+    } catch (ErrorsException e) {
+      errors.merge(e.getErrors());
+      return;
+    }
+
+    try {
+      methodInvoker.invoke(o, parameters);
+    } catch (IllegalAccessException e) {
+      throw new AssertionError(e); // a security manager is blocking us, we're hosed
+    } catch (InvocationTargetException userException) {
+      Throwable cause = userException.getCause() != null
+          ? userException.getCause()
+          : userException;
+      errors.withSource(injectionPoint).errorInjectingMethod(cause);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/com/google/inject/SingleParameterInjector.java b/src/com/google/inject/SingleParameterInjector.java
new file mode 100644
index 0000000..27b6855
--- /dev/null
+++ b/src/com/google/inject/SingleParameterInjector.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 2008 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;
+
+import com.google.inject.spi.Dependency;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import java.util.List;
+
+/**
+ * Resolves a single parameter, to be used in a constructor or method invocation.
+ */
+class SingleParameterInjector<T> {
+  private final Dependency<T> dependency;
+  private final InternalFactory<? extends T> factory;
+
+  SingleParameterInjector(Dependency<T> dependency, InternalFactory<? extends T> factory) {
+    this.dependency = dependency;
+    this.factory = factory;
+  }
+
+  private T inject(Errors errors, InternalContext context) throws ErrorsException {
+    context.setDependency(dependency);
+    try {
+      return factory.get(errors.withSource(dependency), context, dependency);
+    } finally {
+      context.setDependency(null);
+    }
+  }
+
+  /**
+   * Returns an array of parameter values.
+   */
+  static Object[] getAll(Errors errors, InternalContext context,
+      List<SingleParameterInjector<?>> parameterInjectors) throws ErrorsException {
+    if (parameterInjectors == null) {
+      return null;
+    }
+
+    Object[] parameters = new Object[parameterInjectors.size()];
+
+    int i = 0;
+    for (SingleParameterInjector<?> parameterInjector : parameterInjectors) {
+      try {
+        parameters[i++] = parameterInjector.inject(errors, context);
+      } catch (ErrorsException e) {
+        errors.merge(e.getErrors());
+      }
+    }
+
+    errors.throwIfNecessary();
+    return parameters;
+  }
+}
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index 8ae3ad1..cb28901 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -16,8 +16,6 @@
 
 package com.google.inject.internal;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.inject.ConfigurationException;
@@ -37,18 +35,26 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
-import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Formatter;
-import java.util.Iterator;
 import java.util.List;
 
 /**
  * A collection of error messages. If this type is passed as a method parameter, the method is
  * considered to have executed succesfully only if new errors were not added to this collection.
  *
+ * <p>Errors can be chained to provide additional context. To add context, call {@link #withSource}
+ * to create a new Errors instance that contains additional context. All messages added to the
+ * returned instance will contain full context.
+ *
+ * <p>To avoid messages with redundant context, {@link #withSource} should be added sparingly. A
+ * good rule of thumb is to assume a ethod's caller has already specified enough context to
+ * identify that method. When calling a method that's defined in a different context, call that
+ * method with an errors object that includes its context.
+ *
  * @author jessewilson@google.com (Jesse Wilson)
  */
 public final class Errors implements Serializable {
@@ -57,23 +63,26 @@
   private static final boolean allowNullsBadBadBad
       = "I'm a bad hack".equals(System.getProperty("guice.allow.nulls.bad.bad.bad"));
 
-  private List<Message> errors;
-  private final List<Object> sources;
+  private final List<Message> errors;
+  private final Errors parent;
+  private final Object source;
 
   public Errors() {
-    sources = Lists.newArrayList();
-    errors = Lists.newArrayList();
+    this.errors = Lists.newArrayList();
+    this.parent = null;
+    this.source = SourceProvider.UNKNOWN_SOURCE;
   }
 
   public Errors(Object source) {
-    sources = Lists.newArrayList(source);
-    errors = Lists.newArrayList();
+    this.errors = Lists.newArrayList();
+    this.parent = null;
+    this.source = source;
   }
 
   private Errors(Errors parent, Object source) {
-    errors = parent.errors;
-    sources = Lists.newArrayList(parent.sources);
-    sources.add(source);
+    this.errors = parent.errors;
+    this.parent = parent;
+    this.source = source;
   }
 
   /**
@@ -85,15 +94,6 @@
         : new Errors(this, source);
   }
 
-  public void pushSource(Object source) {
-    sources.add(source);
-  }
-
-  public void popSource(Object source) {
-    Object popped = sources.remove(sources.size() - 1);
-    checkArgument(source == popped);
-  }
-
   /**
    * We use a fairly generic error message here. The motivation is to share the
    * same message for both bind time errors:
@@ -107,15 +107,15 @@
    * Otherwise we need to know who's calling when resolving a just-in-time
    * binding, which makes things unnecessarily complex.
    */
-  public Errors missingImplementation(Object keyOrType) {
-    return addMessage("No implementation for %s was bound.", keyOrType);
+  public Errors missingImplementation(Key key) {
+    return addMessage("No implementation for %s was bound.", key);
   }
 
   public Errors converterReturnedNull(String stringValue, Object source,
       TypeLiteral<?> type, MatcherAndConverter matchingConverter) {
     return addMessage("Received null converting '%s' (bound at %s) to %s%n"
         + " using %s.",
-        stringValue, sourceToString(source), type, matchingConverter);
+        stringValue, convert(source), type, matchingConverter);
   }
 
   public Errors conversionTypeError(String stringValue, Object source, TypeLiteral<?> type,
@@ -123,7 +123,7 @@
     return addMessage("Type mismatch converting '%s' (bound at %s) to %s%n"
         + " using %s.%n"
         + " Converter returned %s.",
-        stringValue, sourceToString(source), type, matchingConverter, converted);
+        stringValue, convert(source), type, matchingConverter, converted);
   }
 
   public Errors conversionError(String stringValue, Object source,
@@ -131,7 +131,7 @@
     return addMessage(cause, "Error converting '%s' (bound at %s) to %s%n" 
         + " using %s.%n"
         + " Reason: %s",
-        stringValue, sourceToString(source), type, matchingConverter, cause);
+        stringValue, convert(source), type, matchingConverter, cause);
   }
 
   public Errors ambiguousTypeConversion(String stringValue, Object source, TypeLiteral<?> type,
@@ -140,7 +140,7 @@
         + " %s and%n"
         + " %s.%n"
         + " Please adjust your type converter configuration to avoid overlapping matches.",
-        stringValue, sourceToString(source), type, a, b);
+        stringValue, convert(source), type, a, b);
   }
 
   public Errors bindingToProvider() {
@@ -166,7 +166,7 @@
 
   public Errors missingRuntimeRetention(Object source) {
     return addMessage("Please annotate with @Retention(RUNTIME).%n"
-        + " Bound at %s.", sourceToString(source));
+        + " Bound at %s.", convert(source));
   }
 
   public Errors missingScopeAnnotation() {
@@ -189,7 +189,7 @@
   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, sourceToString(source));
+        + "for abstract types.%n Bound at %s.", type, scopeAnnotation, convert(source));
   }
 
   public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) {
@@ -242,7 +242,7 @@
   }
 
   public Errors bindingAlreadySet(Key<?> key, Object source) {
-    return addMessage("A binding to %s was already configured at %s.", key, sourceToString(source));
+    return addMessage("A binding to %s was already configured at %s.", key, convert(source));
   }
 
   public Errors childBindingAlreadySet(Key<?> key) {
@@ -286,12 +286,15 @@
         expectedType);
   }
 
-  public Errors makeImmutable() {
-    errors = ImmutableList.copyOf(errors);
-    return this;
+  public void throwCreationExceptionIfErrorsExist() {
+    if (!hasErrors()) {
+      return;
+    }
+
+    throw new CreationException(getMessages());
   }
 
-  public void throwConfigurationExceptionIfNecessary() {
+  public void throwConfigurationExceptionIfErrorsExist() {
     if (!hasErrors()) {
       return;
     }
@@ -307,36 +310,37 @@
     throw new ProvisionException(getMessages());
   }
 
-  public void throwCreationExceptionIfErrorsExist() {
-    if (!hasErrors()) {
-      return;
-    }
-
-    makeImmutable();
-    throw new CreationException(getMessages());
-  }
-
   private Message merge(Message message) {
     List<Object> sources = Lists.newArrayList();
-    sources.addAll(this.sources);
+    sources.addAll(getSources());
     sources.addAll(message.getSources());
-    return new Message(stripDuplicates(sources), message.getMessage(), message.getCause());
+    return new Message(sources, message.getMessage(), message.getCause());
   }
 
   public Errors merge(Collection<Message> messages) {
-    if (messages != this.errors) {
-      for (Message message : messages) {
-        errors.add(merge(message));
-      }
+    for (Message message : messages) {
+      errors.add(merge(message));
     }
     return this;
   }
 
   public Errors merge(Errors moreErrors) {
+    if (moreErrors.errors == errors) {
+      return this;
+    }
+
     merge(moreErrors.errors);
     return this;
   }
 
+  public List<Object> getSources() {
+    List<Object> sources = Lists.newArrayList();
+    for (Errors e = this; e != null; e = e.parent) {
+      sources.add(0, e.source);
+    }
+    return sources;
+  }
+
   public void throwIfNecessary() throws ErrorsException {
     if (!hasErrors()) {
       return;
@@ -359,7 +363,7 @@
 
   private Errors addMessage(Throwable cause, String messageFormat, Object... arguments) {
     String message = format(messageFormat, arguments);
-    addMessage(new Message(stripDuplicates(sources), message, cause));
+    addMessage(new Message(getSources(), message, cause));
     return this;
   }
 
@@ -399,28 +403,7 @@
       List<Object> dependencies = errorMessage.getSources();
       for (int i = dependencies.size() - 1; i >= 0; i--) {
         Object source = dependencies.get(i);
-
-        if (source instanceof Dependency) {
-          Dependency<?> dependency = (Dependency<?>) source;
-
-          InjectionPoint injectionPoint = dependency.getInjectionPoint();
-          if (injectionPoint != null) {
-            Member member = injectionPoint.getMember();
-            Class<? extends Member> memberType = MoreTypes.memberType(member);
-            if (memberType == Field.class) {
-              fmt.format("  for field at %s%n", StackTraceElements.forMember(member));
-            } else if (memberType == Method.class || memberType == Constructor.class) {
-              fmt.format("  for parameter %s at %s%n",
-                  dependency.getParameterIndex(), StackTraceElements.forMember(member));
-            } else {
-              throw new AssertionError();
-            }
-          } else {
-            fmt.format("  while locating %s%n", convert(dependency.getKey()));
-          }
-        }
-
-        fmt.format("  at %s%n", sourceToString(source));
+        formatSource(fmt, source);
       }
 
       Throwable cause = errorMessage.getCause();
@@ -506,11 +489,6 @@
   }
 
   private static final Collection<Converter<?>> converters = ImmutableList.of(
-      new Converter<MatcherAndConverter>(MatcherAndConverter.class) {
-        public String toString(MatcherAndConverter m) {
-          return m.toString();
-        }
-      },
       new Converter<Class>(Class.class) {
         public String toString(Class c) {
           return c.getName();
@@ -522,14 +500,13 @@
         }
       },
       new Converter<Key>(Key.class) {
-        public String toString(Key k) {
-          StringBuilder result = new StringBuilder();
-          result.append(k.getTypeLiteral());
-          if (k.getAnnotationType() != null) {
-            result.append(" annotated with ");
-            result.append(k.getAnnotation() != null ? k.getAnnotation() : k.getAnnotationType());
+        public String toString(Key key) {
+          if (key.getAnnotationType() != null) {
+            return key.getTypeLiteral() + " annotated with "
+                + (key.getAnnotation() != null ? key.getAnnotation() : key.getAnnotationType());
+          } else {
+            return key.getTypeLiteral().toString();
           }
-          return result.toString();
         }
       });
 
@@ -542,40 +519,58 @@
     return o;
   }
 
-  /**
-   * This method returns a String that indicates an element source. We do a
-   * best effort to include a line number in this String.
-   */
-  public static String sourceToString(Object source) {
-    checkNotNull(source, "source");
+  public static void formatSource(Formatter formatter, Object source) {
+    if (source instanceof Dependency) {
+      Dependency<?> dependency = (Dependency<?>) source;
+      InjectionPoint injectionPoint = dependency.getInjectionPoint();
+      if (injectionPoint != null) {
+        formatInjectionPoint(formatter, dependency, injectionPoint);
+      } else {
+        formatSource(formatter, dependency.getKey());
+      }
 
-    if (source instanceof InjectionPoint) {
-      return sourceToString(((InjectionPoint) source).getMember());
-    } else if (source instanceof Member) {
-      return StackTraceElements.forMember((Member) source).toString();
+    } else if (source instanceof InjectionPoint) {
+      formatInjectionPoint(formatter, null, (InjectionPoint) source);
+
     } else if (source instanceof Class) {
-      return StackTraceElements.forType(((Class<?>) source)).toString();
+      formatter.format("  at %s%n", StackTraceElements.forType((Class<?>) source));
+
+    } else if (source instanceof Member) {
+      formatter.format("  at %s%n", StackTraceElements.forMember((Member) source));
+
+    } else if (source instanceof Key) {
+      Key<?> key = (Key<?>) source;
+      Type type = key.getTypeLiteral().getType();
+      if (key.getAnnotationType() != null) {
+        formatter.format("  at binding for %s annotated with %s%n", MoreTypes.toString(type),
+            (key.getAnnotation() != null ? key.getAnnotation() : key.getAnnotationType()));
+
+      } else if (type instanceof Class) {
+        formatter.format("  at binding for %s%n", StackTraceElements.forType((Class<?>) type));
+
+      } else {
+          formatter.format("  at binding for %s%n", type);
+
+      }
     } else {
-      return convert(source).toString();
+      formatter.format("  at %s%n", source);
     }
   }
-  
-  /**
-   * Removes consecutive duplicates, so that [A B B C D A] becomes [A B C D A].
-   */
-  private <T> List<T> stripDuplicates(List<T> list) {
-    list = Lists.newArrayList(list);
 
-    Iterator i = list.iterator();
-    if (i.hasNext()) {
-      for (Object last = i.next(), current; i.hasNext(); last = current) {
-        current = i.next();
-        if (last.equals(current)) {
-          i.remove();
-        }
-      }
+  public static void formatInjectionPoint(Formatter formatter, Dependency<?> dependency,
+      InjectionPoint injectionPoint) {
+    Member member = injectionPoint.getMember();
+    Class<? extends Member> memberType = MoreTypes.memberType(member);
+
+    if (memberType == Field.class) {
+      formatter.format("  for field at %s%n", StackTraceElements.forMember(member));
+
+    } else if (dependency != null) {
+      formatter.format("  for parameter %s at %s%n",
+          dependency.getParameterIndex(), StackTraceElements.forMember(member));
+
+    } else {
+      formatSource(formatter, injectionPoint.getMember());
     }
-
-    return ImmutableList.copyOf(list);
   }
 }
diff --git a/src/com/google/inject/internal/FailableCache.java b/src/com/google/inject/internal/FailableCache.java
index 72a4bdf..b252b31 100644
--- a/src/com/google/inject/internal/FailableCache.java
+++ b/src/com/google/inject/internal/FailableCache.java
@@ -33,7 +33,7 @@
       } catch (ErrorsException e) {
         errors.merge(e.getErrors());
       }
-      return errors.hasErrors() ? errors.makeImmutable() : result;
+      return errors.hasErrors() ? errors : result;
     }
   };
 
diff --git a/src/com/google/inject/spi/InjectionPoint.java b/src/com/google/inject/spi/InjectionPoint.java
index 3376a63..f31f627 100644
--- a/src/com/google/inject/spi/InjectionPoint.java
+++ b/src/com/google/inject/spi/InjectionPoint.java
@@ -94,7 +94,7 @@
     } catch (ErrorsException e) {
       errors.merge(e.getErrors());
     }
-    errors.throwConfigurationExceptionIfNecessary();
+    errors.throwConfigurationExceptionIfErrorsExist();
 
     this.dependencies = ImmutableList.<Dependency<?>>of(
         newDependency(key, Nullability.allowsNull(annotations), -1));
@@ -118,7 +118,7 @@
       }
     }
 
-    errors.throwConfigurationExceptionIfNecessary();
+    errors.throwConfigurationExceptionIfErrorsExist();
     return ImmutableList.copyOf(dependencies);
   }
 
@@ -185,7 +185,7 @@
   public static InjectionPoint forConstructorOf(Class<?> type) {
     Errors errors = new Errors(type);
 
-    Constructor<?> found = null;
+    Constructor<?> injectableConstructor = null;
     for (Constructor<?> constructor : type.getDeclaredConstructors()) {
       Inject inject = constructor.getAnnotation(Inject.class);
       if (inject != null) {
@@ -193,33 +193,34 @@
           errors.optionalConstructor(constructor);
         }
 
-        if (found != null) {
+        if (injectableConstructor != null) {
           errors.tooManyConstructors(type);
         }
 
-        found = constructor;
+        injectableConstructor = constructor;
+        checkForMisplacedBindingAnnotations(injectableConstructor, errors);
       }
     }
 
-    errors.throwConfigurationExceptionIfNecessary();
+    errors.throwConfigurationExceptionIfErrorsExist();
 
-    if (found != null) {
-      return new InjectionPoint(found);
+    if (injectableConstructor != null) {
+      return new InjectionPoint(injectableConstructor);
     }
 
-    // If no annotated constructor is found, look for a no-arg constructor
-    // instead.
+    // If no annotated constructor is found, look for a no-arg constructor instead.
     try {
-      Constructor<?> noArgCtor = type.getDeclaredConstructor();
+      Constructor<?> noArgConstructor = type.getDeclaredConstructor();
 
       // Disallow private constructors on non-private classes (unless they have @Inject)
-      if (Modifier.isPrivate(noArgCtor.getModifiers())
+      if (Modifier.isPrivate(noArgConstructor.getModifiers())
           && !Modifier.isPrivate(type.getModifiers())) {
         errors.missingConstructor(type);
         throw new ConfigurationException(errors.getMessages());
       }
 
-      return new InjectionPoint(noArgCtor);
+      checkForMisplacedBindingAnnotations(noArgConstructor, errors);
+      return new InjectionPoint(noArgConstructor);
     } catch (NoSuchMethodException e) {
       errors.missingConstructor(type);
       throw new ConfigurationException(errors.getMessages());
@@ -239,7 +240,7 @@
     Errors errors = new Errors();
     addInjectionPoints(type, Factory.FIELDS, true, sink, errors);
     addInjectionPoints(type, Factory.METHODS, true, sink, errors);
-    errors.throwConfigurationExceptionIfNecessary();;
+    errors.throwConfigurationExceptionIfErrorsExist();
   }
 
   /**
@@ -257,7 +258,15 @@
     Errors errors = new Errors();
     addInjectionPoints(type, Factory.FIELDS, false, sink, errors);
     addInjectionPoints(type, Factory.METHODS, false, sink, errors);
-    errors.throwConfigurationExceptionIfNecessary();
+    errors.throwConfigurationExceptionIfErrorsExist();
+  }
+
+  private static void checkForMisplacedBindingAnnotations(Member member, Errors errors) {
+    Annotation misplacedBindingAnnotation = Annotations.findBindingAnnotation(
+        errors, member, ((AnnotatedElement) member).getAnnotations());
+    if (misplacedBindingAnnotation != null) {
+      errors.misplacedBindingAnnotation(member, misplacedBindingAnnotation);
+    }
   }
 
   private static <M extends Member & AnnotatedElement> void addInjectionPoints(Class<?> type,
@@ -288,7 +297,7 @@
       }
 
       try {
-        injectionPoints.add(factory.create(member));
+        injectionPoints.add(factory.create(member, errors));
       } catch (ConfigurationException ignorable) {
         if (!inject.optional()) {
           errors.merge(ignorable.getErrorMessages());
@@ -306,7 +315,7 @@
       public Field[] getMembers(Class<?> type) {
         return type.getDeclaredFields();
       }
-      public InjectionPoint create(Field member) {
+      public InjectionPoint create(Field member, Errors errors) {
         return new InjectionPoint(member);
       }
     };
@@ -315,13 +324,14 @@
       public Method[] getMembers(Class<?> type) {
         return type.getDeclaredMethods();
       }
-      public InjectionPoint create(Method member) {
+      public InjectionPoint create(Method member, Errors errors) {
+        checkForMisplacedBindingAnnotations(member, errors);
         return new InjectionPoint(member);
       }
     };
 
     M[] getMembers(Class<?> type);
-    InjectionPoint create(M member);
+    InjectionPoint create(M member, Errors errors);
   }
 
   private static final long serialVersionUID = 0;
diff --git a/src/com/google/inject/spi/Message.java b/src/com/google/inject/spi/Message.java
index f417ed3..1273e0e 100644
--- a/src/com/google/inject/spi/Message.java
+++ b/src/com/google/inject/spi/Message.java
@@ -59,7 +59,7 @@
   public String getSource() {
     return sources.isEmpty()
         ? SourceProvider.UNKNOWN_SOURCE.toString()
-        : Errors.sourceToString(sources.get(sources.size() - 1));
+        : Errors.convert(sources.get(sources.size() - 1)).toString();
   }
 
   public List<Object> getSources() {
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index 6c16fb7..a81be5a 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -53,7 +53,6 @@
     suite.addTestSuite(CircularDependencyTest.class);
     suite.addTestSuite(TypeConversionTest.class);
     // suite.addTestSuite(ErrorHandlingTest.class); not a testcase
-    suite.addTestSuite(ErrorMessagesTest.class);
     suite.addTestSuite(GenericInjectionTest.class);
     suite.addTestSuite(ImplicitBindingTest.class);
     suite.addTestSuite(InjectionPointTest.class);
diff --git a/test/com/google/inject/BinderTest.java b/test/com/google/inject/BinderTest.java
index a8f1419..54d459b 100644
--- a/test/com/google/inject/BinderTest.java
+++ b/test/com/google/inject/BinderTest.java
@@ -416,7 +416,7 @@
     } catch (ConfigurationException expected) {
       Asserts.assertContains(expected.getMessage(),
           "1) Cannot inject a Provider that has no type parameter",
-          "at " + Provider.class.getName());
+          "at binding for " + Provider.class.getName());
     }
   }
 }
diff --git a/test/com/google/inject/ErrorMessagesTest.java b/test/com/google/inject/ErrorMessagesTest.java
deleted file mode 100644
index 9043d82..0000000
--- a/test/com/google/inject/ErrorMessagesTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2007 Google Inc. All Rights Reserved.
-
-package com.google.inject;
-
-import static com.google.inject.Asserts.assertContains;
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import java.lang.annotation.Target;
-import junit.framework.TestCase;
-
-/**
- * Tests the error messages produced by Guice.
- *
- * @author Kevin Bourrillion
- */
-public class ErrorMessagesTest extends TestCase {
-
-  private class InnerClass {}
-
-  public void testInjectInnerClass() throws Exception {
-    Injector injector = Guice.createInjector();
-    try {
-      injector.getInstance(InnerClass.class);
-      fail();
-    } catch (Exception expected) {
-      assertContains(expected.getMessage(),
-          "Injecting into inner classes is not supported.",
-          "at " + InnerClass.class.getName() + ".class(ErrorMessagesTest.java:");
-    }
-  }
-
-  public void testInjectLocalClass() throws Exception {
-    class LocalClass {}
-
-    Injector injector = Guice.createInjector();
-    try {
-      injector.getInstance(LocalClass.class);
-      fail();
-    } catch (Exception expected) {
-      assertContains(expected.getMessage(),
-          "Injecting into inner classes is not supported.",
-          "at " + LocalClass.class.getName() + ".class(ErrorMessagesTest.java:");
-    }
-  }
-
-  public void testBindingAnnotationsOnMethodsAndConstructors() {
-    try {
-      Guice.createInjector().getInstance(B.class);
-      fail();
-    } catch (ConfigurationException expected) {
-      assertContains(expected.getMessage(),
-          B.class.getName() + ".injectMe() is annotated with @", Green.class.getName() + "(), ",
-          "but binding annotations should be applied to its parameters instead.",
-          "at " + B.class.getName() + ".injectMe(ErrorMessagesTest.java:");
-    }
-
-    try {
-      Guice.createInjector().getInstance(C.class);
-      fail();
-    } catch (ConfigurationException expected) {
-      assertContains(expected.getMessage(),
-          C.class.getName() + ".<init>() is annotated with @", Green.class.getName() + "(), ",
-          "but binding annotations should be applied to its parameters instead.",
-          "at " + C.class.getName() + ".<init>(ErrorMessagesTest.java:");
-    }
-  }
-
-  static class B {
-    @Inject @Green void injectMe(String greenString) {}
-  }
-
-  static class C {
-    @Inject @Green C(String greenString) {}
-  }
-
-  @Retention(RUNTIME)
-  @Target({ FIELD, PARAMETER, CONSTRUCTOR, METHOD })
-  @BindingAnnotation
-  @interface Green {}
-
-
-  // TODO(kevinb): many many more
-
-}
diff --git a/test/com/google/inject/ImplicitBindingTest.java b/test/com/google/inject/ImplicitBindingTest.java
index 36cdf94..bdd9f06 100644
--- a/test/com/google/inject/ImplicitBindingTest.java
+++ b/test/com/google/inject/ImplicitBindingTest.java
@@ -90,9 +90,9 @@
       Asserts.assertContains(expected.getMessage(),
           "1) No implementation for " + I.class.getName(),
           "annotated with @" + Named.class.getName() + "(value=i) was bound.",
-          "at " + I.class.getName() + ".class(ImplicitBindingTest.java:");
+          "at binding for " + I.class.getName(),
+          " annotated with @" + Named.class.getName() + "(value=i)");
     }
-
   }
 
   static class ProvidedProvider implements Provider<Provided> {
diff --git a/test/com/google/inject/ProvisionExceptionTest.java b/test/com/google/inject/ProvisionExceptionTest.java
index 316a481..4085409 100644
--- a/test/com/google/inject/ProvisionExceptionTest.java
+++ b/test/com/google/inject/ProvisionExceptionTest.java
@@ -19,6 +19,13 @@
 import static com.google.inject.Asserts.assertContains;
 import static com.google.inject.Asserts.assertSimilarWhenReserialized;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import junit.framework.TestCase;
 
 /**
@@ -73,34 +80,6 @@
     }
   }
 
-  public void testProviderExceptions() {
-    try {
-      Guice.createInjector(new AbstractModule() {
-        protected void configure() {
-          bind(D.class).toProvider(DProvider.class);
-        }
-      }).getInstance(D.class);
-      fail();
-    } catch (ProvisionException e) {
-      assertTrue(e.getCause() instanceof UnsupportedOperationException);
-      assertContains(e.getMessage(),
-          "1) Error in custom provider, java.lang.UnsupportedOperationException",
-          "at " + ProvisionExceptionTest.class.getName(), ".configure(ProvisionExceptionTest.java");
-    }
-  }
-
-  public void testBindToTypeExceptions() {
-    try {
-      Guice.createInjector().getInstance(D.class);
-      fail();
-    } catch (ProvisionException e) {
-      assertTrue(e.getCause() instanceof UnsupportedOperationException);
-      assertContains(e.getMessage(),
-          "1) Error injecting constructor, java.lang.UnsupportedOperationException",
-          "at " + D.class.getName() + ".<init>(ProvisionExceptionTest.java:");
-    }
-  }
-
   public void testBindToProviderInstanceExceptions() {
     try {
       Guice.createInjector(new AbstractModule() {
@@ -140,7 +119,8 @@
       fail();
     } catch (ProvisionException e) {
       assertContains(e.getMessage(), "1) User Exception",
-          "at " + ProvisionExceptionTest.class.getName(), ".configure(ProvisionExceptionTest.java");
+          "at binding for ", FProvider.class.getName(), ".class(ProvisionExceptionTest.java",
+          "at binding for ", F.class.getName(), ".class(ProvisionExceptionTest.java:");
     }
   }
 
@@ -182,6 +162,92 @@
     }
   }
 
+  public void testInjectInnerClass() throws Exception {
+    Injector injector = Guice.createInjector();
+    try {
+      injector.getInstance(InnerClass.class);
+      fail();
+    } catch (Exception expected) {
+      assertContains(expected.getMessage(),
+          "Injecting into inner classes is not supported.",
+          "at binding for " + InnerClass.class.getName() + ".class(ProvisionExceptionTest.java:");
+    }
+  }
+
+  public void testInjectLocalClass() throws Exception {
+    class LocalClass {}
+
+    Injector injector = Guice.createInjector();
+    try {
+      injector.getInstance(LocalClass.class);
+      fail();
+    } catch (Exception expected) {
+      assertContains(expected.getMessage(),
+          "Injecting into inner classes is not supported.",
+          "at binding for " + LocalClass.class.getName() + ".class(ProvisionExceptionTest.java:");
+    }
+  }
+
+  public void testBindingAnnotationsOnMethodsAndConstructors() {
+    try {
+      Guice.createInjector().getInstance(MethodWithBindingAnnotation.class);
+      fail();
+    } catch (ConfigurationException expected) {
+      assertContains(expected.getMessage(), MethodWithBindingAnnotation.class.getName()
+          + ".injectMe() is annotated with @", Green.class.getName() + "(), ",
+          "but binding annotations should be applied to its parameters instead.",
+          "at binding for " + MethodWithBindingAnnotation.class.getName() + ".class");
+    }
+
+    try {
+      Guice.createInjector().getInstance(ConstructorWithBindingAnnotation.class);
+      fail();
+    } catch (ConfigurationException expected) {
+      assertContains(expected.getMessage(), ConstructorWithBindingAnnotation.class.getName()
+          + ".<init>() is annotated with @", Green.class.getName() + "(), ",
+          "but binding annotations should be applied to its parameters instead.",
+          "at " + ConstructorWithBindingAnnotation.class.getName() + ".class",
+          "at binding for " + ConstructorWithBindingAnnotation.class.getName() + ".class");
+    }
+  }
+
+  public void testLinkedBindings() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bind(D.class).to(RealD.class);
+      }
+    });
+
+    try {
+      injector.getInstance(D.class);
+      fail();
+    } catch (ProvisionException expected) {
+      assertContains(expected.getMessage(),
+          "at " + RealD.class.getName() + ".<init>(ProvisionExceptionTest.java:",
+          "at binding for " + RealD.class.getName() + ".class(ProvisionExceptionTest.java:",
+          "at binding for " + D.class.getName() + ".class(ProvisionExceptionTest.java:");
+    }
+  }
+
+  public void testProviderKeyBindings() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bind(D.class).toProvider(DProvider.class);
+      }
+    });
+
+    try {
+      injector.getInstance(D.class);
+      fail();
+    } catch (ProvisionException expected) {
+      assertContains(expected.getMessage(),
+          "at binding for " + DProvider.class.getName() + ".class(ProvisionExceptionTest.java:",
+          "at binding for " + D.class.getName() + ".class(ProvisionExceptionTest.java:");
+    }
+  }
+
+  private class InnerClass {}
+
   static class A {
     @Inject
     A(B b) { }
@@ -191,21 +257,38 @@
   }
   static class C {
     @Inject
-    void setD(D d) { }
-  }
-  static class D {
-    D() {
-      throw new UnsupportedOperationException();
-    }
+    void setD(RealD d) { }
   }
   static class E {
     @Inject void setObject(Object o) {
       throw new UnsupportedOperationException();
     }
   }
+
+  static class MethodWithBindingAnnotation {
+    @Inject @Green void injectMe(String greenString) {}
+  }
+
+  static class ConstructorWithBindingAnnotation {
+    @Inject @Green ConstructorWithBindingAnnotation(String greenString) {}
+  }
+
+  @Retention(RUNTIME)
+  @Target({ FIELD, PARAMETER, CONSTRUCTOR, METHOD })
+  @BindingAnnotation
+  @interface Green {}
+
+  interface D {}
+
+  static class RealD implements D {
+    @Inject RealD() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
   static class DProvider implements Provider<D> {
     public D get() {
-      return new D();
+      throw new UnsupportedOperationException();
     }
   }
 
diff --git a/test/com/google/inject/ProxyFactoryTest.java b/test/com/google/inject/ProxyFactoryTest.java
index 4a0d214..9d0dd2e 100644
--- a/test/com/google/inject/ProxyFactoryTest.java
+++ b/test/com/google/inject/ProxyFactoryTest.java
@@ -18,7 +18,6 @@
 package com.google.inject;
 
 import com.google.common.collect.Lists;
-import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import static com.google.inject.matcher.Matchers.annotatedWith;
 import static com.google.inject.matcher.Matchers.any;
@@ -47,8 +46,8 @@
     aspects.add(new MethodAspect(any(), any(), interceptor));
     ProxyFactory factory = new ProxyFactory(aspects);
 
-    ConstructionProxy<Simple> constructionProxy = factory
-        .createConstructionProxy(new Errors(), InjectionPoint.forConstructorOf(Simple.class));
+    ConstructionProxy<Simple> constructionProxy
+        = factory.createConstructionProxy(InjectionPoint.forConstructorOf(Simple.class));
 
     Simple simple = constructionProxy.newInstance();
     simple.invoke();
@@ -81,9 +80,9 @@
     ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<Foo> fooFactory =
-        factory.get(new Errors(), InjectionPoint.forConstructorOf(Foo.class));
+        factory.get(InjectionPoint.forConstructorOf(Foo.class));
     ConstructionProxy<Bar> barFactory =
-        factory.get(new Errors(), InjectionPoint.forConstructorOf(Bar.class));
+        factory.get(InjectionPoint.forConstructorOf(Bar.class));
 
     Foo foo = fooFactory.newInstance();
     Bar bar = barFactory.newInstance();
@@ -135,7 +134,7 @@
     ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<A> constructor =
-        factory.get(new Errors(), InjectionPoint.forConstructorOf(A.class));
+        factory.get(InjectionPoint.forConstructorOf(A.class));
 
     A a = constructor.newInstance(5);
     a.a();
@@ -150,7 +149,7 @@
     ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<A> constructor =
-        factory.get(new Errors(), InjectionPoint.forConstructorOf(A.class));
+        factory.get(InjectionPoint.forConstructorOf(A.class));
 
     A a = constructor.newInstance(5);
     assertEquals(A.class, a.getClass());
@@ -173,7 +172,7 @@
     ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<Counter> constructor =
-        factory.get(new Errors(), InjectionPoint.forConstructorOf(Counter.class));
+        factory.get(InjectionPoint.forConstructorOf(Counter.class));
 
     Counter counter = constructor.newInstance();
     counter.inc();
diff --git a/test/com/google/inject/ScopesTest.java b/test/com/google/inject/ScopesTest.java
index f8ee633..0078a6a 100644
--- a/test/com/google/inject/ScopesTest.java
+++ b/test/com/google/inject/ScopesTest.java
@@ -306,7 +306,7 @@
     } catch (ConfigurationException expected) {
       assertContains(expected.getMessage(),
           "1) More than one scope annotation was found: ",
-          "at " + SingletonAndCustomScoped.class.getName(), ".class(ScopesTest.java:");
+          "at binding for " + SingletonAndCustomScoped.class.getName(), ".class(ScopesTest.java:");
     }
   }