diff --git a/src/resolver.rs b/src/resolver.rs
index a039025..63c5f04 100644
--- a/src/resolver.rs
+++ b/src/resolver.rs
@@ -40,8 +40,9 @@ enum SymKind {
 }
 
 /// General information about a symbol.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug)]
 struct SymInfo {
+    decl: Token,
     kind: SymKind,
     state: SymState,
 }
@@ -64,11 +65,27 @@ impl ResolverState {
         F: FnOnce(&mut Self) -> ResolverResult,
     {
         self.scopes.push(HashMap::new());
-        let result = f(self);
+        let result = f(self).and_then(|_| self.check_unused());
         self.scopes.pop();
         result
     }
 
+    /// Check for unused symbols in the scope. If an unused symbol is found and
+    /// its name does not begin with an underscore, generate an error.
+    fn check_unused(&self) -> ResolverResult {
+        self.scopes[self.scopes.len() - 1]
+            .values()
+            .filter(|v| v.state != SymState::Used)
+            .filter(|v| !v.decl.lexeme.starts_with("_"))
+            .nth(0)
+            .map_or(Ok(()), |v| {
+                self.error(
+                    &v.decl,
+                    "unused symbol; prefix its name with '_' to avoid this error",
+                )
+            })
+    }
+
     /// Try to declare a symbol. If the scope already contains a declaration
     /// for the same name, return an error.
     fn declare(&mut self, name: &Token, kind: SymKind) -> ResolverResult {
@@ -85,6 +102,7 @@ impl ResolverState {
             scope.insert(
                 name.lexeme.clone(),
                 SymInfo {
+                    decl: name.clone(),
                     kind,
                     state: SymState::Declared,
                 },
@@ -160,7 +178,7 @@ impl ResolverState {
     }
 
     /// Return an error.
-    fn error(&mut self, name: &Token, message: &str) -> ResolverResult {
+    fn error(&self, name: &Token, message: &str) -> ResolverResult {
         Err(SloxError::with_token(
             ErrorKind::Parse,
             name,