@@ -208,8 +208,61 @@ object scopes {
208208 else syms.head
209209 }}
210210
211+ // TODO: Use this for more than just types?
212+ //
213+ // NOTE: We might also want to generalize to return more possible candidates than one
214+ // (1 works fine for types, but maybe not for fields/methods/functions/...)
215+ private def didYouMean (nameNotFound : String , candidates : => Set [String ], specificHeuristic : String => Option [String ] = _ => None )(using E : ErrorReporter ): Unit =
216+ // Priority 1: A specific heuristic
217+ specificHeuristic(nameNotFound) orElse {
218+ // Priority 2: Exact case-insensitive match
219+ candidates
220+ .find { name => name.toUpperCase == nameNotFound.toUpperCase }
221+ .map { exactMatch => pp " Did you mean $exactMatch? " }
222+ .orElse {
223+ // Priority 3: Edit distance
224+ val threshold = ((nameNotFound.length + 2 ) max 3 ) / 3
225+
226+ candidates
227+ .toSeq
228+ .flatMap { candidate =>
229+ effekt.util.editDistance(nameNotFound, candidate, threshold).map((_, candidate))
230+ }
231+ .sorted
232+ .headOption
233+ .map { case (_, name) => pp " Did you mean $name? " }
234+ }
235+ } foreach { msg => E .info(msg) }
236+
237+ // NOTE: Most of these should be covered by the edit distance anyway...
238+ private def didYouMeanTypeHeuristic (name : String ): Option [String ] = name match {
239+ case " Boolean" | " boolean" => Some (pp " Did you mean Bool? " )
240+ case " Str" | " str" => Some (pp " Did you mean String? " )
241+ case " Integer" | " Int32" | " Int64" | " I32" | " I64" | " i32" | " i64" =>
242+ Some (pp " Did you mean Int? " )
243+ case " UInt32" | " UInt64" | " UInt" | " U32" | " U64" | " u32" | " u64" =>
244+ Some (pp " Effekt only supports signed integers, did you mean Int? " )
245+ case " Int8" | " UInt8" | " I8" | " U8" | " i8" | " u8" =>
246+ Some (pp " Did you mean Byte (8 bits) or Char (32 bits)? " )
247+ case " Float" | " float" | " F64" | " F32" | " f64" | " f32" =>
248+ Some (pp " Effekt only supports 64bit floating numbers, did you mean Double? " )
249+ case " Maybe" | " maybe" => Some (pp " Did you mean Option? " )
250+ case " Void" | " void" => Some (pp " Did you mean Unit? " )
251+ case _ => None
252+ }
253+
211254 def lookupType (id : IdRef )(using E : ErrorReporter ): TypeSymbol =
212- lookupTypeOption(id.path, id.name) getOrElse { E .abort(pp " Could not resolve type ${id}" ) }
255+ lookupTypeOption(id.path, id.name) getOrElse {
256+ // If we got here, we could not find an exact match.
257+ // But let's try and find a close one!
258+ def availableTypes = all(id.path, scope) {
259+ _.types.keys.toList
260+ }.flatten.toSet
261+
262+ didYouMean(id.name, availableTypes, didYouMeanTypeHeuristic)
263+
264+ E .abort(pp " Could not resolve type $id" )
265+ }
213266
214267 def lookupTypeOption (path : List [String ], name : String )(using E : ErrorReporter ): Option [TypeSymbol ] =
215268 first(path, scope) { _.types.get(name) }
0 commit comments