Skip to content

Commit

Permalink
First experiment to add CodeActions to typepal
Browse files Browse the repository at this point in the history
- For every undefined name, suggestions are generated for similar names
  in similar roles. TODO: maybe type could also be used to filter
  suggestions.
- An elegant/efficient implementation of Levensthein distance has been
  made to measure edit distance.
- When this approachs works, the other errors generated directly by
  TypePal will be reviewed for possible additions of fixes.
  • Loading branch information
PaulKlint committed Nov 9, 2024
1 parent 668be7f commit b5806da
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 39 deletions.
36 changes: 24 additions & 12 deletions src/analysis/typepal/Solver.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ module analysis::typepal::Solver
/*
Implementation of the ISolver interface; this is the API of TypePal's constraint solver
*/
import Set;
import Node;
import Map;
extend analysis::typepal::Collector;
extend analysis::typepal::Messenger;

import Exception;
import IO;
import List;
import Location;
import Map;
import Message;
import Node;
import ParseTree;
import Type;
import Set;
import String;
import Message;
import Exception;
//import util::Benchmark;

extend analysis::typepal::Collector;
extend analysis::typepal::Messenger;
import Type;
import analysis::typepal::StringSimilarity;
import util::IDEServices;

void checkAllTypesAvailable(TModel tm){
for(tup: <loc _, str _, str _, IdRole _, loc _, DefInfo defInfo> <- tm.defines){
Expand Down Expand Up @@ -1517,13 +1518,13 @@ Solver newSolver(map[str,Tree] namedTrees, TModel tm){
foundDefs = scopeGraph.lookup(u);
} catch NoBinding(): {
roles = size(u.idRoles) > 5 ? "" : intercalateOr([prettyRole(idRole) | idRole <- u.idRoles]);
messages += error("Undefined <roles> `<getOrgId(u)>`", u.occ);
messages += error("Undefined <roles> `<getOrgId(u)>`", u.occ, fixes=undefinedNameProposals(u, tm));
}
}

for(u <- notYetDefinedUses){
roles = size(u.idRoles) > 5 ? "" : intercalateOr([prettyRole(idRole) | idRole <- u.idRoles]);
messages += error("Undefined <roles> `<getOrgId(u)>`", u.occ);
messages += error("Undefined <roles> `<getOrgId(u)>`", u.occ, fixes=undefinedNameProposals(u, tm));
}

error_locations = { src | error(_,loc src) <- messages };
Expand Down Expand Up @@ -1704,3 +1705,14 @@ Solver newSolver(map[str,Tree] namedTrees, TModel tm){

return thisSolver;
}

// CodeActions for errors generated by Solver

list[CodeAction] undefinedNameProposals(Use u, TModel tm)
=
[ action(
title="Replace undefined `<getOrgId(u)>`",
edits=[changed([replace(u.occ, prop)])]
)
| str prop <- similarNames(getOrgId(u), u.idRoles, tm, 3)
];
57 changes: 30 additions & 27 deletions src/analysis/typepal/StringSimilarity.rsc
Original file line number Diff line number Diff line change
@@ -1,41 +1,37 @@
module analysis::typepal::StringSimilarity

import String;
import List;
import String;
import analysis::typepal::TModel;

@synopsis{Tryadic minimum function on integers}
int min(int a, int b, int c)
= a < b ? (a < c ? a : c) : (b < c ? b : c);

@synopsis{Calculate the Levenshtein distance of 2 strings}
@memo
int lev(str a, str b)
= lev(a, 0, b, 0);

@memo
private int lev(str a, int ia, str b, int ib){
if(ib == size(b)) return size(a) - ia;
if(ia == size(a)) return size(b) - ib;
if(a[ia] == b[ib]) return lev(a, ia+1, b, ib+1);

return 1 + min(lev(a, ia+1, b, ib),
lev(a, ia, b, ib+1),
lev(a, ia+1, b, ib+1));
int lev(str a, str b){
int sizea = size(a);
int sizeb = size(b);

@memo
int lev(int ia, int ib){
if(ib == sizeb) return sizea - ia;
if(ia == sizea) return sizeb - ib;
if(a[ia] == b[ib]) return lev(ia+1, ib+1);

return 1 + min(lev(ia+1, ib),
lev(ia, ib+1),
lev(ia+1, ib+1));
}

return lev(0, 0);
}

// @memo
// private int lev(str a, int ia, int lena, str b, int ib, int lenb){
// if(lenb == 0) return lena;
// if(lena == 0) return lenb;
// if(a[ia] == b[ib]) return lev(a, ia+1, lena-1, b, ib+1, lenb-1);
// Tests for `lev`

// return 1 + min(lev(a, ia+1, lena-1, b, ib, lenb),
// lev(a, ia, lena, b, ib+1, lenb-1),
// lev(a, ia+1, lena-1, b, ib+1, lenb-1));
// }
test bool levCommutative(str a, str b) = lev(a, b) == lev(b, a);

test bool lev0(str a, str b) = lev(a, b) == lev(b, a);

test bool levx(str a, str b, str c) = lev(a, b) == lev(c + a, c + b);
test bool levLeftAdditive(str a, str b, str c) = lev(a, b) == lev(c + a, c + b);

test bool lev1() = lev("kitten", "sitting") == 3;
test bool lev2() = lev("kelm", "hello") == 3;
Expand All @@ -47,10 +43,17 @@ test bool lev7() = lev("page", "pope") == 2;
test bool lev8() = lev("december", "january") == 8;
test bool lev9() = lev("march", "may") == 3;

// Similarity functions to be used by TypePal

@synopsis{WordSim represents one word from the vocabulary and its similariy to the original word}
alias WordSim = tuple[str word, int sim];

@synopsis{Compute list of words from vocabulary, that are similar to give word w with at most maxDistance edits}
list[str] similarWords(str w, list[str] vocabulary, int maxDistance)
= sort([ <v, d> | str v <- vocabulary, d := lev(w, v), d <= maxDistance ], bool (WordSim x, WordSim y){ return x.sim < y.sim;}).word;

value main() = similarWords("ac", ["a", "ab", "ac", "x"], 10);
@synopsis{Find in TModel tm, names similar to w, in gives roles, with at most maxDistance edits}
list[str] similarNames(str w, set[IdRole] idRoles, TModel tm, int maxDistance){
vocabulary = [ d.orgId | d <- tm.defines, d.idRole in idRoles ];
return similarWords(w, vocabulary, maxDistance);
}

0 comments on commit b5806da

Please sign in to comment.