Skip to content

Conversation

@jiribenes
Copy link
Contributor

@jiribenes jiribenes commented Oct 27, 2025

Add a system to help the users when the compiler cannot find a name of a type:

Heuristic 1: Some types are named differently in Effekt than in other languages

Screenshot 2025-10-27 at 15 06 41

This specific one (Boolean instead of Bool) is done often by students and LLMs alike.

Heuristic 2: Try case-insensitive comparison

Screenshot 2025-10-27 at 15 06 26

Heuristic 3: Try full on edit distance!

Screenshot 2025-10-27 at 22 31 43

@jiribenes jiribenes changed the title Add 'did you mean' heuristics for type names Add 'did you mean' help for mistaken type names Oct 27, 2025
Comment on lines +258 to +260
def availableTypes = all(id.path, scope) {
_.types.keys.toList
}.flatten.toSet
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very much not a fan of this, can we do better here somehow?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you expect? We somehow need to gather all types from all scopes. The only thing that looks a bit weird at first to me is the .keys, but that makes complete sense.

Comment on lines +216 to +235
// Priority 1: A specific heuristic
specificHeuristic(nameNotFound) orElse {
// Priority 2: Exact case-insensitive match
candidates
.find { name => name.toUpperCase == nameNotFound.toUpperCase }
.map { exactMatch => pp"Did you mean $exactMatch?" }
.orElse {
// Priority 3: Edit distance
val threshold = ((nameNotFound.length + 2) max 3) / 3

candidates
.toSeq
.flatMap { candidate =>
effekt.util.editDistance(nameNotFound, candidate, threshold).map((_, candidate))
}
.sorted
.headOption
.map { case (_, name) => pp"Did you mean $name?" }
}
} foreach { msg => E.info(msg) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried very hard to structure this sanely, but it's still not what I'd prefer, is there a better way?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is not too bad. It is modular enough to also use it for terms. Well done!

Of course the general concern of better-error-messages-complicate-compiler still applies. At least you managed to keep the actual change in lookupType to just a few lines.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[prob. overengineering, but since you asked:]
Maybe there'd be a sufficiently common pattern between the two others that could be extracted into a foo(candidates: ...)(comp: (String,String) => ?) => Option[String] function?
Also depending on how much we care about efficiency here.

Comment on lines 33 to 35
val d = Array.ofDim[Int](N + 1, M + 1)
for (i <- 0 to N) { d(i)(0) = i }
for (j <- 0 to M) { d(0)(j) = j }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this performs too badly, we can always use two or three rows of the array only... (it just makes it harder to read)

@jiribenes jiribenes requested a review from b-studios October 27, 2025 22:12
Comment on lines +216 to +235
// Priority 1: A specific heuristic
specificHeuristic(nameNotFound) orElse {
// Priority 2: Exact case-insensitive match
candidates
.find { name => name.toUpperCase == nameNotFound.toUpperCase }
.map { exactMatch => pp"Did you mean $exactMatch?" }
.orElse {
// Priority 3: Edit distance
val threshold = ((nameNotFound.length + 2) max 3) / 3

candidates
.toSeq
.flatMap { candidate =>
effekt.util.editDistance(nameNotFound, candidate, threshold).map((_, candidate))
}
.sorted
.headOption
.map { case (_, name) => pp"Did you mean $name?" }
}
} foreach { msg => E.info(msg) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is not too bad. It is modular enough to also use it for terms. Well done!

Of course the general concern of better-error-messages-complicate-compiler still applies. At least you managed to keep the actual change in lookupType to just a few lines.

Comment on lines +258 to +260
def availableTypes = all(id.path, scope) {
_.types.keys.toList
}.flatten.toSet
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you expect? We somehow need to gather all types from all scopes. The only thing that looks a bit weird at first to me is the .keys, but that makes complete sense.

@@ -0,0 +1,3 @@
module boolean_wrong_name

def alwaysTrue(): Boolean = true No newline at end of file
Copy link
Collaborator

@b-studios b-studios Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have it for terms, we can add a hint True ~> true for @BinderDavid ;)

And S(S(Z())) ~> "Did you mean 2?"

Copy link
Contributor

@marzipankaiser marzipankaiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already commented above, LGTM!
Great also for people that sometimes forget which direction we changed Bool/ean...
(👉😅👈)

@jiribenes
Copy link
Contributor Author

Thanks for the reviews!

@jiribenes jiribenes merged commit 3f61f80 into master Oct 30, 2025
2 checks passed
@jiribenes jiribenes deleted the jiribenes/type-name-did-you-mean branch October 30, 2025 10:58
danielflat pushed a commit that referenced this pull request Oct 31, 2025
Add a system to help the users when the compiler cannot find a name of a
type:

#### Heuristic 1: Some types are named differently in Effekt than in
other languages

<img width="469" height="100" alt="Screenshot 2025-10-27 at 15 06 41"
src="https://github.com/user-attachments/assets/8b2a4a64-1860-4e5c-b1ec-05b9c3ecc609"
/>

This specific one (`Boolean` instead of `Bool`) is done often by
students and LLMs alike.

#### Heuristic 2: Try case-insensitive comparison

<img width="390" height="137" alt="Screenshot 2025-10-27 at 15 06 26"
src="https://github.com/user-attachments/assets/893777c5-247c-486f-8a94-704c740fe69d"
/>

#### Heuristic 3: Try full on edit distance!

<img width="359" height="135" alt="Screenshot 2025-10-27 at 22 31 43"
src="https://github.com/user-attachments/assets/9570b6ef-2222-4132-9575-e8773edf8f3b"
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants