{"record":{"id":"9f0cba1b12072a45","repo":"clojure/clojure","slug":"feature-should-be-a-keyword","errorCode":null,"errorMessage":"Feature should be a keyword: {}","messagePattern":"Feature should be a keyword: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/jvm/clojure/lang/LispReader.java","lineNumber":1526,"sourceCode":"\tif(RT.booleanCast(READ_COND_ENV.deref()) && opts instanceof IPersistentMap)\n    {\n        Object readCond = ((IPersistentMap) opts).valAt(OPT_READ_COND);\n        return COND_PRESERVE.equals(readCond);\n    }\n    else\n        return false;\n}\n\npublic static class ConditionalReader extends AFn {\n\n\tfinal static private Object READ_STARTED = new Object();\n\tfinal static public Keyword DEFAULT_FEATURE = Keyword.intern(null, \"default\");\n\tfinal static public IPersistentSet RESERVED_FEATURES =\n\t\tRT.set(Keyword.intern(null, \"else\"), Keyword.intern(null, \"none\"));\n\n\tpublic static boolean hasFeature(Object feature, Object opts) {\n\t\tif (! (feature instanceof Keyword))\n\t\t\tthrow Util.runtimeException(\"Feature should be a keyword: \" + feature);\n\n\t\tif(DEFAULT_FEATURE.equals(feature))\n\t\t\treturn true;\n\n\t\tIPersistentSet custom = (IPersistentSet) ((IPersistentMap)opts).valAt(OPT_FEATURES);\n\t\treturn custom != null && custom.contains(feature);\n\t}\n\n\tpublic static Object readCondDelimited(PushbackReader r, boolean splicing, Object opts, Object pendingForms) {\n\t\tObject result = READ_STARTED;\n\t\tObject form; // The most recently ready form\n\t\tboolean toplevel = (pendingForms == null);\n\t\tpendingForms = ensurePending(pendingForms);\n\n\t\tfinal int firstline =\n\t\t\t\t(r instanceof LineNumberingPushbackReader) ?\n\t\t\t\t\t\t((LineNumberingPushbackReader) r).getLineNumber() : -1;\n","sourceCodeStart":1508,"sourceCodeEnd":1544,"githubUrl":"https://github.com/clojure/clojure/blob/f3b143341d6efc6428b523b2eaa099a6cc99156e/src/jvm/clojure/lang/LispReader.java#L1508-L1544","documentation":"In reader conditionals (#?), the feature expression must be a keyword such as :clj, :cljs, or :default. hasFeature validates this before checking the feature table and throws if the read feature is any other type. Features are matched against DEFAULT_FEATURE (:default) and the caller-supplied :features option set.","triggerScenarios":"Evaluating (reader-conditional/read with cond) where a feature form reads as a non-keyword, e.g. (clj/reader-conditional ...) misuse or a form like #?(:\"clj\" ...) or feature position holding a symbol/number.","commonSituations":"Writing custom reader-conditional evaluation with features passed as strings instead of keywords; typo'd or generated conditional forms where the feature slot contains a symbol like clj instead of :clj.","solutions":["Quote the feature as a keyword: use :clj not clj or \"clj\".","When calling hasFeature programmatically, pass (keyword f) after validating f is a string/symbol.","Validate the feature table contents before reading: every feature in the opts :features set must be a Keyword."],"exampleFix":"// before\n(hasFeature \"clj\" opts) ; string feature\n// after\n(hasFeature (keyword \"clj\") opts) ; -> :clj","handlingStrategy":"type-guard","validationCode":"(defn feature-keyword? [f] (keyword? f))","typeGuard":"(defn as-feature [f]\n  (cond (keyword? f) f\n        (or (symbol? f) (string? f)) (keyword f)\n        :else (throw (ex-info \"feature must be keyword-able\" {:feature f}))))","tryCatchPattern":"(try (hasFeature f opts) (catch RuntimeException e :unsupported-feature))","preventionTips":["Always use keywords (:clj :cljs :default) in conditional feature positions","Convert string/symbol features with (keyword x) before passing to reader APIs","Validate the :features option set contains only keywords"],"tags":["clojure","reader","reader-conditional","keywords"],"backgroundTag":"type-mismatch","analyzedSha":"f3b143341d6efc6428b523b2eaa099a6cc99156e","analyzedAt":"2026-09-09T12:04:58.961Z","contentChangedAt":"2026-09-09T12:04:58.961Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}