diff --git a/source/dub/dependencyresolver.d b/source/dub/dependencyresolver.d
index 432399c..6b42d80 100644
--- a/source/dub/dependencyresolver.d
+++ b/source/dub/dependencyresolver.d
@@ -14,6 +14,7 @@
 import std.array : appender, array;
 import std.conv : to;
 import std.exception : enforce;
+import std.typecons : Nullable;
 import std.string : format, indexOf, lastIndexOf;
 
 
@@ -41,7 +42,8 @@
 		CONFIG config;
 
 		hash_t toHash() const nothrow @trusted {
-			size_t ret = typeid(string).getHash(&pack);
+			size_t ret = pack.hashOf();
+			//size_t ret = typeid(string).getHash(&pack);
 			ret ^= typeid(CONFIG).getHash(&config);
 			return ret;
 		}
@@ -123,7 +125,7 @@
 		config_indices[] = 0;
 
 		visited = null;
-		sizediff_t validateConfigs(TreeNode parent, ref string error)
+		sizediff_t validateConfigs(TreeNode parent, ref ConflictError error)
 		{
 			import std.algorithm : max;
 
@@ -160,7 +162,7 @@
 					}
 					// choose another parent config to avoid the invalid child
 					if (parentidx > maxcpi) {
-						error = format("Package %s contains invalid dependency %s (no version candidates)", parent.pack, ch.pack);
+						error = ConflictError(ConflictError.Kind.invalidDependency, parent, ch, CONFIG.invalid);
 						logDiagnostic("%s (ci=%s)", error, parentidx);
 						maxcpi = parentidx;
 					}
@@ -175,13 +177,13 @@
 
 						// if we are at the root level, we can safely skip the maxcpi computation and instead choose another childidx config
 						if (parentbase == root_base_pack) {
-							error = format("No match for dependency %s %s of %s", ch.pack, ch.configs, parent.pack);
+							error = ConflictError(ConflictError.Kind.noRootMatch, parent, ch, config);
 							return childidx;
 						}
 
 						if (childidx > maxcpi) {
 							maxcpi = max(childidx, parentidx);
-							error = format("Dependency %s -> %s %s mismatches with selected version %s", parent.pack, ch.pack, ch.configs, config);
+							error = ConflictError(ConflictError.Kind.childMismatch, parent, ch, config);
 							logDebug("%s (ci=%s)", error, maxcpi);
 						}
 
@@ -196,7 +198,7 @@
 			return maxcpi;
 		}
 
-		string first_error;
+		Nullable!ConflictError first_error;
 		size_t loop_counter = 0;
 
 		// Leave the possibility to opt-out from the loop limit
@@ -213,9 +215,9 @@
 
 			// check if the current combination of configurations works out
 			visited = null;
-			string error;
+			ConflictError error;
 			auto conflict_index = validateConfigs(root, error);
-			if (first_error is null) first_error = error;
+			if (first_error.isNull) first_error = error;
 
 			// print out current iteration state
 			logDebug("Interation (ci=%s) %s", conflict_index, {
@@ -249,7 +251,7 @@
 				else break;
 			}
 			if (config_indices.all!"a==0") {
-				if (throw_on_failure) throw new Exception("Could not find a valid dependency tree configuration: "~first_error);
+				if (throw_on_failure) throw new Exception(format("Could not find a valid dependency tree configuration: %s", first_error.get));
 				else return null;
 			}
 		}
@@ -283,6 +285,36 @@
 			if (p !in required)
 				configs.remove(p);
 	}
+
+	private struct ConflictError {
+		enum Kind {
+			none,
+			noRootMatch,
+			childMismatch,
+			invalidDependency
+		}
+
+		Kind kind;
+		TreeNode parent;
+		TreeNodes child;
+		CONFIG config;
+
+		string toString()
+		const {
+			final switch (kind) {
+				case Kind.none: return "no error";
+				case Kind.noRootMatch:
+					return "No match for dependency %s %s of %s"
+						.format(child.pack, child.configs, parent.pack);
+				case Kind.childMismatch:
+					return "Dependency %s -> %s %s mismatches with selected version %s"
+						.format(parent.pack, child.pack, child.configs, config);
+				case Kind.invalidDependency:
+					return "Package %s contains invalid dependency %s (no version candidates)"
+						.format(parent.pack, child.pack);
+			}
+		}
+	}
 }
 
 enum DependencyType {