From fbd79b258f4c55467c61a58530321cd7a3ec10c8 Mon Sep 17 00:00:00 2001 From: Vushu Date: Thu, 23 Mar 2023 00:50:46 +0100 Subject: [PATCH] refactoring match algorithm to not use recursion for UFCS (#736) --- dsymbol/src/dsymbol/ufcs.d | 101 ++++++++++-------- .../alias_this_on_function.d | 10 ++ .../expected_alias_this_on_function_test.txt | 2 + ...xpected_plenty_alias_this_defined_test.txt | 2 + ...pected_plenty_alias_this_defined_test2.txt | 6 ++ .../plenty_alias_this_defined.d | 24 +++++ tests/tc_ufcs_alias_this_completion/run.sh | 25 +++++ 7 files changed, 123 insertions(+), 47 deletions(-) create mode 100644 tests/tc_ufcs_alias_this_completion/alias_this_on_function.d create mode 100644 tests/tc_ufcs_alias_this_completion/expected_alias_this_on_function_test.txt create mode 100644 tests/tc_ufcs_alias_this_completion/expected_plenty_alias_this_defined_test.txt create mode 100644 tests/tc_ufcs_alias_this_completion/expected_plenty_alias_this_defined_test2.txt create mode 100644 tests/tc_ufcs_alias_this_completion/plenty_alias_this_defined.d create mode 100755 tests/tc_ufcs_alias_this_completion/run.sh diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 96d4377..bdb8bad 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -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; } diff --git a/tests/tc_ufcs_alias_this_completion/alias_this_on_function.d b/tests/tc_ufcs_alias_this_completion/alias_this_on_function.d new file mode 100644 index 0000000..59f6606 --- /dev/null +++ b/tests/tc_ufcs_alias_this_completion/alias_this_on_function.d @@ -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 +} \ No newline at end of file diff --git a/tests/tc_ufcs_alias_this_completion/expected_alias_this_on_function_test.txt b/tests/tc_ufcs_alias_this_completion/expected_alias_this_on_function_test.txt new file mode 100644 index 0000000..ec2ae92 --- /dev/null +++ b/tests/tc_ufcs_alias_this_completion/expected_alias_this_on_function_test.txt @@ -0,0 +1,2 @@ +identifiers +ufcsMatching F diff --git a/tests/tc_ufcs_alias_this_completion/expected_plenty_alias_this_defined_test.txt b/tests/tc_ufcs_alias_this_completion/expected_plenty_alias_this_defined_test.txt new file mode 100644 index 0000000..739d9b9 --- /dev/null +++ b/tests/tc_ufcs_alias_this_completion/expected_plenty_alias_this_defined_test.txt @@ -0,0 +1,2 @@ +identifiers +ufcsA F diff --git a/tests/tc_ufcs_alias_this_completion/expected_plenty_alias_this_defined_test2.txt b/tests/tc_ufcs_alias_this_completion/expected_plenty_alias_this_defined_test2.txt new file mode 100644 index 0000000..76c1017 --- /dev/null +++ b/tests/tc_ufcs_alias_this_completion/expected_plenty_alias_this_defined_test2.txt @@ -0,0 +1,6 @@ +identifiers +ufcsA F +ufcsB F +ufcsC F +ufcsD F +ufcsE F diff --git a/tests/tc_ufcs_alias_this_completion/plenty_alias_this_defined.d b/tests/tc_ufcs_alias_this_completion/plenty_alias_this_defined.d new file mode 100644 index 0000000..57837cd --- /dev/null +++ b/tests/tc_ufcs_alias_this_completion/plenty_alias_this_defined.d @@ -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 +} \ No newline at end of file diff --git a/tests/tc_ufcs_alias_this_completion/run.sh b/tests/tc_ufcs_alias_this_completion/run.sh new file mode 100755 index 0000000..6fac638 --- /dev/null +++ b/tests/tc_ufcs_alias_this_completion/run.sh @@ -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 \ No newline at end of file