refactoring match algorithm to not use recursion for UFCS (#736)

This commit is contained in:
Vushu 2023-03-23 00:50:46 +01:00 committed by GitHub
parent 19e019a57b
commit fbd79b258f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 47 deletions

View File

@ -41,7 +41,7 @@ enum string[string] INTEGER_PROMOTIONS = [
"dchar": "uint",
];
enum MAX_RECURSION_DEPTH = 50;
enum MAX_NUMBER_OF_MATCHING_RUNS = 50;
private const(DSymbol)* deduceSymbolType(const(DSymbol)* symbol)
{
@ -127,7 +127,7 @@ private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPos
return tokenCursorResult;
}
private void getUFCSSymbols(T, Y)(ref T localAppender, ref Y globalAppender, Scope* completionScope, size_t cursorPosition)
private void getUFCSSymbols(T, Y)(scope ref T localAppender, scope ref Y globalAppender, Scope* completionScope, size_t cursorPosition)
{
Scope* currentScope = completionScope.getScopeByCursor(cursorPosition);
@ -177,7 +177,7 @@ private void getUFCSSymbols(T, Y)(ref T localAppender, ref Y globalAppender, Sco
}
}
DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, ref const(Token)[] tokens, size_t cursorPosition)
DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, scope ref const(Token)[] tokens, size_t cursorPosition)
{
TokenCursorResult tokenCursorResult = getCursorToken(tokens, cursorPosition);
@ -257,60 +257,74 @@ private DSymbol*[] getUFCSSymbolsForParenCompletion(const(DSymbol)* symbolType,
}
private bool willImplicitBeUpcasted(const(DSymbol)* from, const(DSymbol)* to)
private bool willImplicitBeUpcasted(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) significantSymbolType)
{
if (from is null || to is null || to.functionParameters.empty || to.functionParameters.front.type is null) {
return false;
}
string fromTypeName = from.name.data;
string toTypeName = to.functionParameters.front.type.name.data;
string fromTypeName = significantSymbolType.name.data;
string toTypeName = incomingSymbolType.name.data;
return typeWillBeUpcastedTo(fromTypeName, toTypeName);
}
private bool typeWillBeUpcastedTo(string from, string to) {
private bool typeWillBeUpcastedTo(string from, string to)
{
if (auto promotionType = from in INTEGER_PROMOTIONS)
return *promotionType == to;
return false;
}
private bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbol, int recursionDepth)
bool isNonConstrainedTemplate(scope ref const(DSymbol) symbolType)
{
// For now we are only resolving the first alias this symbol
// when multiple alias this are supported, we can rethink another solution
if (beforeDotType.aliasThisSymbols.empty || beforeDotType.aliasThisSymbols.front == beforeDotType)
{
return false;
}
//Incrementing depth count to ensure we don't run into an infinite loop
recursionDepth++;
return isCallableWithArg(incomingSymbol, beforeDotType.aliasThisSymbols.front.type, false, recursionDepth);
return symbolType.kind is CompletionKind.typeTmpParam;
}
bool isNonConstrainedTemplate(const(DSymbol)* incomingSymbol){
return incomingSymbol.functionParameters.front.type !is null
&& incomingSymbol.functionParameters.front.type.kind is CompletionKind.typeTmpParam;
private bool matchesWithTypeOfPointer(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) significantSymbolType)
{
return incomingSymbolType.qualifier == SymbolQualifier.pointer
&& significantSymbolType.qualifier == SymbolQualifier.pointer
&& incomingSymbolType.type is significantSymbolType.type;
}
private bool matchesWithTypeOfPointer(const(DSymbol)* incomingSymbol, const(DSymbol)* cursorSymbolType) {
return incomingSymbol.functionParameters.front.type.qualifier == SymbolQualifier.pointer
&& cursorSymbolType.qualifier == SymbolQualifier.pointer
&& incomingSymbol.functionParameters.front.type.type is cursorSymbolType.type;
}
private bool matchesWithTypeOfArray(const(DSymbol)* incomingSymbol, const(DSymbol)* cursorSymbolType) {
return incomingSymbol.functionParameters.front.type.qualifier == SymbolQualifier.array
private bool matchesWithTypeOfArray(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) cursorSymbolType)
{
return incomingSymbolType.qualifier == SymbolQualifier.array
&& cursorSymbolType.qualifier == SymbolQualifier.array
&& incomingSymbol.functionParameters.front.type.type is cursorSymbolType.type;
&& incomingSymbolType.type is cursorSymbolType.type;
}
private bool typeMatchesWith(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) significantSymbolType) {
return incomingSymbolType is significantSymbolType
|| isNonConstrainedTemplate(incomingSymbolType)
|| matchesWithTypeOfArray(incomingSymbolType, significantSymbolType)
|| matchesWithTypeOfPointer(incomingSymbolType, significantSymbolType)
|| willImplicitBeUpcasted(incomingSymbolType, significantSymbolType);
}
private bool matchSymbolType(const(DSymbol)* incomingSymbolType, const(DSymbol)* significantSymbolType) {
auto currentSignificantSymbolType = significantSymbolType;
uint numberOfRetries = 0;
do
{
if (typeMatchesWith(*incomingSymbolType, *currentSignificantSymbolType)){
return true;
}
if (currentSignificantSymbolType.aliasThisSymbols.empty || currentSignificantSymbolType is currentSignificantSymbolType.aliasThisSymbols.front){
return false;
}
numberOfRetries++;
// For now we are only resolving the first alias this symbol
// when multiple alias this are supported, we can rethink another solution
currentSignificantSymbolType = currentSignificantSymbolType.aliasThisSymbols.front.type;
}
while(numberOfRetries <= MAX_NUMBER_OF_MATCHING_RUNS);
return false;
}
/**
* Params:
* incomingSymbol = the function symbol to check if it is valid for UFCS with `beforeDotType`.
@ -320,25 +334,18 @@ private bool matchesWithTypeOfArray(const(DSymbol)* incomingSymbol, const(DSymbo
* `true` if `incomingSymbols`' first parameter matches `beforeDotType`
* `false` otherwise
*/
bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false, int recursionDepth = 0)
bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false)
{
if (incomingSymbol is null
|| beforeDotType is null
|| isGlobalScope && incomingSymbol.protection is tok!"private" // don't show private functions if we are in global scope
|| recursionDepth > MAX_RECURSION_DEPTH)
|| isGlobalScope && incomingSymbol.protection is tok!"private") // don't show private functions if we are in global scope
{
return false;
}
if (incomingSymbol.kind is CompletionKind.functionName && !incomingSymbol.functionParameters.empty && incomingSymbol.functionParameters.front.type)
{
return beforeDotType is incomingSymbol.functionParameters.front.type
|| isNonConstrainedTemplate(incomingSymbol)
|| matchesWithTypeOfArray(incomingSymbol, beforeDotType)
|| matchesWithTypeOfPointer(incomingSymbol, beforeDotType)
|| willImplicitBeUpcasted(beforeDotType, incomingSymbol)
|| matchAliasThis(beforeDotType, incomingSymbol, recursionDepth);
return matchSymbolType(incomingSymbol.functionParameters.front.type, beforeDotType);
}
return false;
}

View File

@ -0,0 +1,10 @@
struct S { S* s; S get() { return *s; } alias get this; }
void ufcsMatching(S value) {}
void ufcsNonMatching(int value) {}
void main()
{
S s;
s.ufcs
}

View File

@ -0,0 +1,2 @@
identifiers
ufcsMatching F

View File

@ -0,0 +1,2 @@
identifiers
ufcsA F

View File

@ -0,0 +1,6 @@
identifiers
ufcsA F
ufcsB F
ufcsC F
ufcsD F
ufcsE F

View File

@ -0,0 +1,24 @@
struct A { int x; }
struct B { A a; alias a this; }
struct C { B b; alias b this; }
struct D { C c; alias c this; }
struct E { D d; alias d this; }
struct F { E e; alias e this; }
void ufcsA(A a) {}
void ufcsB(B b) {}
void ufcsC(C c) {}
void ufcsD(D d) {}
void ufcsE(E e) {}
void testA()
{
A a;
a.ufcs // should only give ufcsA
}
void testE()
{
E e;
e.ufcs // should give all the ufcs methods
}

View File

@ -0,0 +1,25 @@
set -e
set -u
#TEST CASE 0
SOURCE_FILE_0=alias_this_on_function.d
ACTUAL_FILE_NAME_0="actual_alias_this_on_function_test.txt"
EXPECTED_FILE_NAME_0="expected_alias_this_on_function_test.txt"
../../bin/dcd-client $1 -c152 $SOURCE_FILE_0 > $ACTUAL_FILE_NAME_0
diff $ACTUAL_FILE_NAME_0 $EXPECTED_FILE_NAME_0 --strip-trailing-cr
#TEST CASE 1
SOURCE_FILE_1=plenty_alias_this_defined.d
ACTUAL_FILE_NAME_1="actual_plenty_alias_this_defined_test.txt"
EXPECTED_FILE_NAME_1="expected_plenty_alias_this_defined_test.txt"
../../bin/dcd-client $1 -c305 $SOURCE_FILE_1 > $ACTUAL_FILE_NAME_1
diff $ACTUAL_FILE_NAME_1 $EXPECTED_FILE_NAME_1 --strip-trailing-cr
#TEST CASE 2
ACTUAL_FILE_NAME_2="actual_plenty_alias_this_defined_test2.txt"
EXPECTED_FILE_NAME_2="expected_plenty_alias_this_defined_test2.txt"
../../bin/dcd-client $1 -c363 $SOURCE_FILE_1 > $ACTUAL_FILE_NAME_2
diff $ACTUAL_FILE_NAME_2 $EXPECTED_FILE_NAME_2 --strip-trailing-cr