diff --git a/README.md b/README.md index 1bc4397f..b12e6b8f 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,9 @@ the issue.) * *auto* declarations (Mostly) * *with* statements * Simple UFCS suggestions for concrete types and fundamental types. + * Dot chaining with other UFCS functions * Not working: - * UFCS completion for templates, literals, aliased types, UFCS function arguments, and '.' chaining with other UFCS functions. + * UFCS completion for templates, literals, aliased types, UFCS function arguments. * UFCS calltips * Autocompletion of declarations with template arguments (This will work to some extent, but it won't do things like replace T with int) * Determining the type of an enum member when no base type is specified, but the first member has an initializer diff --git a/dsymbol/src/dsymbol/conversion/first.d b/dsymbol/src/dsymbol/conversion/first.d index 6cad7ff8..6ed49e12 100644 --- a/dsymbol/src/dsymbol/conversion/first.d +++ b/dsymbol/src/dsymbol/conversion/first.d @@ -132,6 +132,14 @@ final class FirstPass : ASTVisitor currentSymbol.acSymbol.protection = protection.current; currentSymbol.acSymbol.doc = makeDocumentation(dec.comment); currentSymbol.acSymbol.qualifier = SymbolQualifier.func; + foreach (sc; dec.storageClasses) + { + if (sc.token.type == tok!"ref") + { + currentSymbol.acSymbol.returnIsRef = true; + break; + } + } istring lastComment = this.lastComment; this.lastComment = istring.init; @@ -172,6 +180,11 @@ final class FirstPass : ASTVisitor block.startLocation, null); scope(exit) popSymbol(); + if (exp.returnRefType == ReturnRefType.ref_) + { + currentSymbol.acSymbol.returnIsRef = true; + } + pushScope(block.startLocation, block.endLocation); scope (exit) popScope(); processParameters(currentSymbol, exp.returnType, diff --git a/dsymbol/src/dsymbol/symbol.d b/dsymbol/src/dsymbol/symbol.d index 6e2f79ec..cf81a989 100644 --- a/dsymbol/src/dsymbol/symbol.d +++ b/dsymbol/src/dsymbol/symbol.d @@ -443,7 +443,8 @@ struct DSymbol bool, "_flag8", 1, bool, "_flag9", 1, bool, "_flag10", 1, - uint, "", 3, + bool, "_flag11", 1, + uint, "", 2, )); // dfmt on @@ -463,6 +464,8 @@ struct DSymbol alias parameterIsOut = _flag9; /// Only valid for parameters: the parameter has storage class `in` alias parameterIsIn = _flag10; + /// If the return type is a ref + alias returnIsRef = _flag11; deprecated bool isPointer() { diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 618c5a8f..2333cb07 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -4,15 +4,124 @@ import dsymbol.symbol; import dsymbol.scope_; import dsymbol.builtin.names; import dsymbol.utils; -import dparse.lexer : tok, Token; +import dparse.lexer : tok, Token, isStringLiteral, isNumberLiteral; +import dparse.strings; import std.functional : unaryFun; import std.algorithm; +import std.algorithm.searching : countUntil; import std.array; import std.range; import std.string; import std.regex; import containers.hashset : HashSet; import std.experimental.logger; +import std.typecons : nullable, Nullable; + +alias SortedTokens = SortedRange!(const(Token)[], "a < b"); +struct ScopeLookupContext +{ + Scope* completionScope; + const(Token)[] exprTokens; + size_t cursorPosition; + const(DSymbol)* getSymbolByName(string name) + { + return completionScope.getFirstSymbolByNameAndCursor(istring(name), cursorPosition); + } +} + +bool compatibleType(DSymbol* sym, ref const(Token) argToken, ScopeLookupContext scopeComplentionContext) +{ + if (!sym.type) + { + return false; + } + + switch (argToken.type) + { + mixin(STRING_LITERAL_CASES); + return sym.type.name == "string"; + case tok!"true": + case tok!"false": + return sym.type.name is getBuiltinTypeName(tok!"bool"); + + case tok!"intLiteral": + case tok!"longLiteral": + case tok!"uintLiteral": + case tok!"ulongLiteral": + // integer literals + return + // integers + sym.type.name is getBuiltinTypeName(tok!"int") || + sym.type.name is getBuiltinTypeName(tok!"uint") || + sym.type.name is getBuiltinTypeName(tok!"long") || + sym.type.name is getBuiltinTypeName(tok!"ulong") || + sym.type.name is getBuiltinTypeName(tok!"byte") || + sym.type.name is getBuiltinTypeName(tok!"ubyte") || + sym.type.name is getBuiltinTypeName(tok!"short") || + sym.type.name is getBuiltinTypeName(tok!"ushort") || + + // floats + sym.type.name is getBuiltinTypeName(tok!"float") || + sym.type.name is getBuiltinTypeName(tok!"double") || + sym.type.name is getBuiltinTypeName(tok!"real") || + + // complex + sym.type.name is getBuiltinTypeName(tok!"cfloat") || + sym.type.name is getBuiltinTypeName(tok!"cdouble") || + sym.type.name is getBuiltinTypeName(tok!"creal"); + + case tok!"floatLiteral": + case tok!"doubleLiteral": + case tok!"realLiteral": + return + // floats + sym.type.name is getBuiltinTypeName(tok!"float") || + sym.type.name is getBuiltinTypeName(tok!"double") || + sym.type.name is getBuiltinTypeName(tok!"real") || + + sym.type.name is getBuiltinTypeName(tok!"cfloat") || + sym.type.name is getBuiltinTypeName(tok!"cdouble") || + sym.type.name is getBuiltinTypeName(tok!"creal"); + + case tok!"ifloatLiteral": + case tok!"idoubleLiteral": + case tok!"irealLiteral": + // imaginary literals + return + // imaginary + sym.type.name is getBuiltinTypeName(tok!"ifloat") || + sym.type.name is getBuiltinTypeName(tok!"idouble") || + sym.type.name is getBuiltinTypeName(tok!"ireal") || + + // complex + sym.type.name is getBuiltinTypeName(tok!"cfloat") || + sym.type.name is getBuiltinTypeName(tok!"cdouble") || + sym.type.name is getBuiltinTypeName(tok!"creal"); + + default: + // Not a primitive type eg. Identifier + // Doing type looking up + if (argToken.text) + { + auto found = scopeComplentionContext.getSymbolByName(argToken.text); + if (found) + { + return sym.type is found.type; + } + } + return false; + } +} + +struct ExpressionInfo +{ + const(DSymbol)* type; + const(Token)* significantToken; + bool assumingLvalue; // We only assume else we need to do life time analysis. + bool isFromFunction; + const(Token)[] arguments; + string name; +} enum CompletionContext { @@ -21,48 +130,291 @@ enum CompletionContext ParenCompletion, } - struct TokenCursorResult { CompletionContext completionContext; istring functionName; - Token significantToken; + const(Token)[] expressionTokens; string partialIdentifier; } // https://dlang.org/spec/type.html#implicit-conversions enum string[string] INTEGER_PROMOTIONS = [ - "bool": "byte", - "byte": "int", - "ubyte": "int", - "short": "int", - "ushort": "int", - "char": "int", - "wchar": "int", - "dchar": "uint", - - // found in test case extra/tc_ufcs_all_kinds: - "int": "float", - "uint": "float", - "long": "float", - "ulong": "float", - - "float": "double", - "double": "real", - ]; + "bool": "byte", + "byte": "int", + "ubyte": "int", + "short": "int", + "ushort": "int", + "char": "int", + "wchar": "int", + "dchar": "uint", + + // found in test case extra/tc_ufcs_all_kinds: + "int": "float", + "uint": "float", + "long": "float", + "ulong": "float", + + "float": "double", + "double": "real", +]; enum MAX_NUMBER_OF_MATCHING_RUNS = 50; -private const(DSymbol)* deduceSymbolTypeByToken(Scope* completionScope, scope ref const(Token) significantToken, size_t cursorPosition) +private const(Token)* findUFCSBaseToken(const(Token)[] tokens, out const(Token)[] arguments) +{ + if (tokens.empty) + return null; + + int depth = 0; + + // Walk backwards to skip nested parentheses/brackets/braces + for (size_t i = tokens.length; i-- > 0;) + { + auto t = tokens[i].type; + + // Handle closing of nested scopes + if (t is tok!")" || t is tok!"]" || t is tok!"}") + { + depth++; + continue; + } + + // Handle opening of nested scopes + if (t is tok!"(" || t is tok!"[" || t is tok!"{") + { + depth--; + continue; + } + + if (depth != 0) + continue; + + // If we hit a literal or identifier, that's likely our base + if (isStringLiteral(t) || t is tok!"intLiteral" || t is tok!"floatLiteral") + { + return &tokens[i]; + } + if (t is tok!"identifier") + { + // If this is a function call, then extract the arguments + if (tokens.length >= 2 && i + 1 < tokens.length && tokens[i + 1] == tok!"(" && tokens[$ - 1] == tok!")") + { + auto argTokens = tokens[i + 2 .. $ - 1]; // get whats inside ( ... ) + foreach (argToken; argTokens) + { + if (argToken == tok!",") + { + continue; + } + arguments ~= argToken; + } + } + return &tokens[i]; + } + + // Stop at anything else that breaks the expression (operators, keywords, etc.) + return &tokens[i + 1]; + } + + // If we never returned inside the loop, the first token is the base + return &tokens[0]; +} + +private const(Token)* findExpressionBase(const(Token)[] tokens) +{ + foreach (i, t; tokens) + { + // literals are always a base + if (isStringLiteral(t.type) || isNumberLiteral(t.type) || t.type is tok!"identifier") + return &tokens[i]; + } + return tokens.ptr; // fallback +} + +/// Resolves a symbol in a UFCS chain during type deduction. +/// This is intentionally permissive (name-based lookup) +/// Used only for type deduction, completion filtering will be done in a later step +private const(DSymbol)* resolveUFCSChainSymbol( + Scope* completionScope, + ExpressionInfo beforeDotType, + istring name, + size_t cursorPosition) +{ + Appender!(DSymbol*[]) local; + Appender!(DSymbol*[]) global; + + getUFCSSymbols(local, global, completionScope, cursorPosition); + + auto allSymbols = local.data ~ global.data; + + const(DSymbol)* fallback = null; + + foreach (sym; allSymbols) + { + if (sym.name != name) + { + continue; + } + // Prefer a symbol that actually matches the type + if (sym.isCallableWithArg(beforeDotType)) + { + return sym; + } + + // Otherwise remember a fallback (loose match) + if (fallback is null) + { + fallback = sym; + } + } + + return fallback; +} + +private Nullable!ExpressionInfo deduceExpressionType( + Scope* completionScope, + const(Token)[] exprTokens, + size_t cursorPosition) +{ + ExpressionInfo info; + + if (exprTokens.empty) + { + return Nullable!ExpressionInfo.init; + } + + info.significantToken = findUFCSBaseToken(exprTokens, info.arguments); + if (isStringLiteral(info.significantToken.type)) + { + info.type = completionScope.getFirstSymbolByNameAndCursor( + symbolNameToTypeName(STRING_LITERAL_SYMBOL_NAME), cursorPosition); + return nullable(info); + } + + auto scopeLookupContext = ScopeLookupContext(completionScope, exprTokens, cursorPosition); + info.type = deduceSymbolTypeByToken(info, scopeLookupContext); + + if (info.type is null) + { + return Nullable!ExpressionInfo.init; + } + + // 2. Walk through the expression left → right + for (size_t i = 1; i < exprTokens.length; i++) + { + auto t = exprTokens[i].type; + + // ---- Handle function call: foo() ---- + if (t is tok!"(") + { + // Skip to matching ')' + int depth = 1; + size_t j = i + 1; + + while (j < exprTokens.length && depth > 0) + { + if (exprTokens[j].type is tok!"(") + depth++; + else if (exprTokens[j].type is tok!")") + depth--; + + j++; + } + + // Function call → move to return type + if (info.type !is null && info.type.type !is null) + { + info.type = info.type.type; + } + + i = j - 1; + continue; + } + + // ---- Handle dot call + if (t is tok!"." && + i + 1 < exprTokens.length && + exprTokens[i + 1].type is tok!"identifier") + { + auto name = istring(exprTokens[i + 1].text); + + auto match = resolveUFCSChainSymbol( + completionScope, + info, + name, + cursorPosition + ); + + if (match is null) + { + return Nullable!ExpressionInfo.init; + } + + i++; // skip identifier + continue; + } + } + return nullable(info); +} + +private const(DSymbol)* deduceSymbolTypeByToken(ref ExpressionInfo info, ScopeLookupContext scopeCompletionContext) { - //Literal type deduction - if (significantToken.type is tok!"stringLiteral"){ - return completionScope.getFirstSymbolByNameAndCursor(symbolNameToTypeName(STRING_LITERAL_SYMBOL_NAME), cursorPosition); + const(DSymbol)* symbol = null; + auto found = scopeCompletionContext.completionScope.getSymbolsByNameAndCursor( + istring(info.significantToken.text), scopeCompletionContext.cursorPosition); + + if (found.empty) + { + return null; } - const(DSymbol)* symbol = completionScope.getFirstSymbolByNameAndCursor(istring(significantToken.text), cursorPosition); + if (found.length == 1) + { + symbol = found.front; + } + else if (found.length > 1) + { + // If we have more functions then we must have overloaded function + if (info.arguments.length > 0) + { + // we need to match with the arguments accordingly if any + // we assume that the first param matches since it's a UFCS call, hence why we - 1. + auto filtered = found.find!((i => max(i.functionParameters.length - 1, 0) == info + .arguments.length)); + if (filtered.length == 1) + { + // There is only 1 solution + symbol = filtered.front; + } + else if (filtered.length > 1) + { + bool allMatch = false; + foreach (DSymbol* sym; filtered) + { + allMatch = false; + foreach (idx, p; sym.functionParameters[1 .. $]) // we assume that the first param matches since it's a UFCS call, hence why we start with 1. + { + allMatch = compatibleType(p, info.arguments[idx], scopeCompletionContext); + if (!allMatch) + { + trace(sym.name," doesn't match with the arguments"); + break; + } + } + if (allMatch) + { + symbol = sym; + trace("Found the right overloaded function ", sym.type.name); + return sym.type; + } + } + } + } + } - if (symbol is null) { + if (symbol is null) + { return null; } @@ -73,16 +425,14 @@ private const(DSymbol)* deduceSymbolTypeByToken(Scope* completionScope, scope re || symbolType.kind == CompletionKind.aliasName)) { if (symbolType.type is null - || symbolType.type is symbolType - || symbolType.name.data == "string") // special case for string - { + || symbolType.type is symbolType) // special case for string + { break; } //look at next type to deduce symbolType = symbolType.type; } - return symbolType; } @@ -96,36 +446,80 @@ private bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol) tok!"void")); } -private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPosition) +const(Token)* findUFCSExpressionStart(SortedTokens tokens) +{ + int depth = 0; + + for (size_t i = tokens.length; i-- > 0;) + { + auto t = tokens[i].type; + + // Handle nesting + if (t is tok!")" || t is tok!"]" || t is tok!"}") + { + depth++; + continue; + } + + if (t is tok!"(" || t is tok!"[" || t is tok!"{") + { + depth--; + continue; + } + + if (depth != 0) + continue; + + // Allow chaining: f.papa().x + if (t is tok!"." || + t is tok!"identifier" || + t is tok!"stringLiteral") + { + continue; + } + + // Stop when hitting something that breaks expression + return &tokens[i + 1]; + } + + // Entire range is the expression + return &tokens[0]; +} + +private TokenCursorResult getCursorToken(Scope* completionScope, const(Token)[] tokens, size_t cursorPosition) { - auto sortedTokens = assumeSorted(tokens); - auto sortedBeforeTokens = sortedTokens.lowerBound(cursorPosition); + SortedTokens sortedTokens = assumeSorted(tokens); + SortedTokens sortedBeforeTokens = sortedTokens.lowerBound(cursorPosition); TokenCursorResult tokenCursorResult; - if (sortedBeforeTokens.empty) { + if (sortedBeforeTokens.empty) + { return tokenCursorResult; } - // move before identifier for + // Handle partially completed if (sortedBeforeTokens[$ - 1].type is tok!"identifier") { tokenCursorResult.partialIdentifier = sortedBeforeTokens[$ - 1].text; sortedBeforeTokens = sortedBeforeTokens[0 .. $ - 1]; } - if (sortedBeforeTokens.length >= 2 - && sortedBeforeTokens[$ - 1].type is tok!"." - && (sortedBeforeTokens[$ - 2].type is tok!"identifier" || sortedBeforeTokens[$ - 2].type is tok!"stringLiteral")) + // Handle dot completion + if (!sortedBeforeTokens.empty && + sortedBeforeTokens[$ - 1].type is tok!".") { - // Check if it's UFCS dot completion - auto expressionTokens = getExpression(sortedBeforeTokens); - if(expressionTokens[0] !is sortedBeforeTokens[$ - 2]){ - // If the expression is invalid as a dot token we return + const(Token)* exprStart = findUFCSExpressionStart(sortedBeforeTokens); + + if (exprStart is null) return tokenCursorResult; - } - tokenCursorResult.significantToken = sortedBeforeTokens[$ - 2]; + size_t start = exprStart - tokens.ptr; + size_t end = (&sortedBeforeTokens[$ - 1]) - tokens.ptr; + + auto exprTokens = tokens[start .. end]; + + tokenCursorResult.expressionTokens = exprTokens; tokenCursorResult.completionContext = CompletionContext.DotCompletion; return tokenCursorResult; } @@ -140,14 +534,15 @@ private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPos } auto slicedAtParen = sortedBeforeTokens[0 .. index]; - if (slicedAtParen.length >= 4 - && slicedAtParen[$ - 4].type is tok!"identifier" + + // Also allowing ) for ufcs function chaining + if (slicedAtParen.length >= 3 && slicedAtParen[$ - 3].type is tok!"." && slicedAtParen[$ - 2].type is tok!"identifier" && slicedAtParen[$ - 1].type is tok!"(") { + tokenCursorResult.expressionTokens = slicedAtParen[0 .. $ - 3].array; tokenCursorResult.completionContext = CompletionContext.ParenCompletion; - tokenCursorResult.significantToken = slicedAtParen[$ - 4]; tokenCursorResult.functionName = istring(slicedAtParen[$ - 2].text); return tokenCursorResult; } @@ -209,7 +604,7 @@ private void getUFCSSymbols(T, Y)(scope ref T localAppender, scope ref Y globalA DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, scope ref const(Token)[] tokens, size_t cursorPosition) { - TokenCursorResult tokenCursorResult = getCursorToken(tokens, cursorPosition); + TokenCursorResult tokenCursorResult = getCursorToken(completionScope, tokens, cursorPosition); if (tokenCursorResult.completionContext is CompletionContext.UnknownCompletion) { @@ -217,14 +612,15 @@ DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, scope ref const(Token return []; } - const(DSymbol)* deducedSymbolType = deduceSymbolTypeByToken(completionScope, tokenCursorResult.significantToken, cursorPosition); + Nullable!ExpressionInfo deducedSymbolType = deduceExpressionType(completionScope, tokenCursorResult + .expressionTokens, cursorPosition); - if (deducedSymbolType is null) + if (deducedSymbolType.isNull) { return []; } - if (deducedSymbolType.isInvalidForUFCSCompletion) + if (deducedSymbolType.get().type.isInvalidForUFCSCompletion) { trace("CursorSymbolType isn't valid for UFCS completion"); return []; @@ -232,16 +628,18 @@ DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, scope ref const(Token if (tokenCursorResult.completionContext == CompletionContext.ParenCompletion) { - return getUFCSSymbolsForParenCompletion(deducedSymbolType, completionScope, tokenCursorResult.functionName, cursorPosition); + return getUFCSSymbolsForParenCompletion(deducedSymbolType.get(), completionScope, tokenCursorResult + .functionName, cursorPosition); } else { - return getUFCSSymbolsForDotCompletion(deducedSymbolType, completionScope, cursorPosition, tokenCursorResult.partialIdentifier); + return getUFCSSymbolsForDotCompletion(deducedSymbolType.get(), completionScope, cursorPosition, tokenCursorResult + .partialIdentifier); } } -private DSymbol*[] getUFCSSymbolsForDotCompletion(const(DSymbol)* symbolType, Scope* completionScope, size_t cursorPosition, string partial) +private DSymbol*[] getUFCSSymbolsForDotCompletion(ExpressionInfo symbolType, Scope* completionScope, size_t cursorPosition, string partial) { // local appender FilteredAppender!((DSymbol* a) => @@ -259,7 +657,7 @@ private DSymbol*[] getUFCSSymbolsForDotCompletion(const(DSymbol)* symbolType, Sc return localAppender.data ~ globalAppender.data; } -private DSymbol*[] getUFCSSymbolsForParenCompletion(const(DSymbol)* symbolType, Scope* completionScope, istring searchWord, size_t cursorPosition) +private DSymbol*[] getUFCSSymbolsForParenCompletion(ExpressionInfo symbolType, Scope* completionScope, istring searchWord, size_t cursorPosition) { // local appender FilteredAppender!(a => a.isCallableWithArg(symbolType) && a.name.among(searchWord), DSymbol*[]) localAppender; @@ -272,7 +670,8 @@ private DSymbol*[] getUFCSSymbolsForParenCompletion(const(DSymbol)* symbolType, } -private bool willImplicitBeUpcasted(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) significantSymbolType) +private bool willImplicitBeUpcasted(scope ref const(DSymbol) incomingSymbolType, scope ref const( + DSymbol) significantSymbolType) { string fromTypeName = significantSymbolType.name.data; string toTypeName = incomingSymbolType.name.data; @@ -311,16 +710,24 @@ private int getIntegerTypeSize(string type) { switch (type) { - // ordered by subjective frequency of use, since the compiler may use that - // for optimization. - case "int", "uint": return 4; - case "long", "ulong": return 8; - case "byte", "ubyte": return 1; - case "short", "ushort": return 2; - case "dchar": return 4; - case "wchar": return 2; - case "char": return 1; - default: return 0; + // ordered by subjective frequency of use, since the compiler may use that + // for optimization. + case "int", "uint": + return 4; + case "long", "ulong": + return 8; + case "byte", "ubyte": + return 1; + case "short", "ushort": + return 2; + case "dchar": + return 4; + case "wchar": + return 2; + case "char": + return 1; + default: + return 0; } } @@ -342,14 +749,16 @@ bool isNonConstrainedTemplate(scope ref const(DSymbol) symbolType) return symbolType.kind is CompletionKind.typeTmpParam; } -private bool matchesWithTypeOfPointer(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) significantSymbolType) +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 matchesWithTypeOfArray(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) cursorSymbolType) +private bool matchesWithTypeOfArray(scope ref const(DSymbol) incomingSymbolType, scope ref const( + DSymbol) cursorSymbolType) { return incomingSymbolType.qualifier == SymbolQualifier.array && cursorSymbolType.qualifier == SymbolQualifier.array @@ -357,21 +766,40 @@ private bool matchesWithTypeOfArray(scope ref const(DSymbol) incomingSymbolType, } -private bool typeMatchesWith(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) significantSymbolType) { +private bool matchStringLikeTypes(scope ref const(DSymbol) incomingSymbolType, scope ref const( + DSymbol) significantSymbolType) +{ + if ((incomingSymbolType.name.data == "string" || incomingSymbolType.name.data == "wstring" + || incomingSymbolType.name.data == "dstring") && (significantSymbolType.name.data == "string" || significantSymbolType + .name.data == "wstring" + || significantSymbolType.name.data == "dstring")) + { + return true; + } + return false; +} + +private bool typeMatchesWith(scope ref const(DSymbol) incomingSymbolType, scope ref const(DSymbol) significantSymbolType) +{ return incomingSymbolType is significantSymbolType - || isNonConstrainedTemplate(incomingSymbolType) + || isNonConstrainedTemplate( + incomingSymbolType) || matchesWithTypeOfArray(incomingSymbolType, significantSymbolType) - || matchesWithTypeOfPointer(incomingSymbolType, significantSymbolType); + || matchesWithTypeOfPointer(incomingSymbolType, significantSymbolType) + || matchStringLikeTypes(incomingSymbolType, significantSymbolType); + } -private bool matchSymbolType(const(DSymbol)* firstParameter, const(DSymbol)* significantSymbolType) { +private bool matchSymbolType(const(DSymbol)* firstParameter, const(DSymbol)* significantSymbolType) +{ auto currentSignificantSymbolType = significantSymbolType; uint numberOfRetries = 0; do { - if (typeMatchesWith(*firstParameter.type, *currentSignificantSymbolType)) { + if (typeMatchesWith(*firstParameter.type, *currentSignificantSymbolType)) + { return true; } @@ -379,7 +807,9 @@ private bool matchSymbolType(const(DSymbol)* firstParameter, const(DSymbol)* sig && willImplicitBeUpcasted(*firstParameter.type, *currentSignificantSymbolType)) return true; - if (currentSignificantSymbolType.aliasThisSymbols.empty || currentSignificantSymbolType is currentSignificantSymbolType.aliasThisSymbols.front){ + if (currentSignificantSymbolType.aliasThisSymbols.empty || currentSignificantSymbolType is currentSignificantSymbolType + .aliasThisSymbols.front) + { return false; } @@ -388,7 +818,7 @@ private bool matchSymbolType(const(DSymbol)* firstParameter, const(DSymbol)* sig // when multiple alias this are supported, we can rethink another solution currentSignificantSymbolType = currentSignificantSymbolType.aliasThisSymbols.front.type; } - while(numberOfRetries <= MAX_NUMBER_OF_MATCHING_RUNS); + while (numberOfRetries <= MAX_NUMBER_OF_MATCHING_RUNS); return false; } @@ -401,18 +831,20 @@ private bool matchSymbolType(const(DSymbol)* firstParameter, const(DSymbol)* sig * `true` if `incomingSymbols`' first parameter matches `beforeDotType` * `false` otherwise */ -bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false) +bool isCallableWithArg(const(DSymbol)* incomingSymbol, ExpressionInfo beforeDotType, bool isGlobalScope = false) { if (incomingSymbol is null - || beforeDotType is null + || beforeDotType.type is null || 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) + if (incomingSymbol.kind is CompletionKind.functionName && !incomingSymbol.functionParameters.empty && incomingSymbol + .functionParameters.front.type) { - return matchSymbolType(incomingSymbol.functionParameters.front, beforeDotType); + auto firstParam = incomingSymbol.functionParameters.front; + return matchSymbolType(firstParam, beforeDotType.type); } return false; } diff --git a/tests/tc_ufcs_function_chaining_calltip_completion/expected.txt b/tests/tc_ufcs_function_chaining_calltip_completion/expected.txt new file mode 100644 index 00000000..661186b9 --- /dev/null +++ b/tests/tc_ufcs_function_chaining_calltip_completion/expected.txt @@ -0,0 +1,2 @@ +calltips +int subtract(int x, int y) diff --git a/tests/tc_ufcs_function_chaining_calltip_completion/file.d b/tests/tc_ufcs_function_chaining_calltip_completion/file.d new file mode 100644 index 00000000..921da512 --- /dev/null +++ b/tests/tc_ufcs_function_chaining_calltip_completion/file.d @@ -0,0 +1,12 @@ +int add(int x, int y) { + return x + y; +} + +int subtract(int x, int y) { + return x - y; +} + +void main(){ + int x = 5; + x.add(5).subtract( +} diff --git a/tests/tc_ufcs_function_chaining_calltip_completion/run.sh b/tests/tc_ufcs_function_chaining_calltip_completion/run.sh new file mode 100755 index 00000000..7525e0dc --- /dev/null +++ b/tests/tc_ufcs_function_chaining_calltip_completion/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 -c133 file.d > actual.txt +diff actual.txt expected.txt --strip-trailing-cr diff --git a/tests/tc_ufcs_function_chaining_completion/expected_completion_test.txt b/tests/tc_ufcs_function_chaining_completion/expected_completion_test.txt new file mode 100644 index 00000000..9271886d --- /dev/null +++ b/tests/tc_ufcs_function_chaining_completion/expected_completion_test.txt @@ -0,0 +1,13 @@ +identifiers +alignof k +bar F +baz F +foo F +init k +mangleof k +qux F +refFoo F +refFoo2 F +sizeof k +stringof k +tupleof k diff --git a/tests/tc_ufcs_function_chaining_completion/expected_completion_test2.txt b/tests/tc_ufcs_function_chaining_completion/expected_completion_test2.txt new file mode 100644 index 00000000..e38cf1dd --- /dev/null +++ b/tests/tc_ufcs_function_chaining_completion/expected_completion_test2.txt @@ -0,0 +1,7 @@ +identifiers +bar F +baz F +foo F +qux F +refFoo F +refFoo2 F diff --git a/tests/tc_ufcs_function_chaining_completion/expected_completion_test3.txt b/tests/tc_ufcs_function_chaining_completion/expected_completion_test3.txt new file mode 100644 index 00000000..e38cf1dd --- /dev/null +++ b/tests/tc_ufcs_function_chaining_completion/expected_completion_test3.txt @@ -0,0 +1,7 @@ +identifiers +bar F +baz F +foo F +qux F +refFoo F +refFoo2 F diff --git a/tests/tc_ufcs_function_chaining_completion/expected_completion_test4.txt b/tests/tc_ufcs_function_chaining_completion/expected_completion_test4.txt new file mode 100644 index 00000000..e38cf1dd --- /dev/null +++ b/tests/tc_ufcs_function_chaining_completion/expected_completion_test4.txt @@ -0,0 +1,7 @@ +identifiers +bar F +baz F +foo F +qux F +refFoo F +refFoo2 F diff --git a/tests/tc_ufcs_function_chaining_completion/expected_completion_test5.txt b/tests/tc_ufcs_function_chaining_completion/expected_completion_test5.txt new file mode 100644 index 00000000..9271886d --- /dev/null +++ b/tests/tc_ufcs_function_chaining_completion/expected_completion_test5.txt @@ -0,0 +1,13 @@ +identifiers +alignof k +bar F +baz F +foo F +init k +mangleof k +qux F +refFoo F +refFoo2 F +sizeof k +stringof k +tupleof k diff --git a/tests/tc_ufcs_function_chaining_completion/expected_completion_test6.txt b/tests/tc_ufcs_function_chaining_completion/expected_completion_test6.txt new file mode 100644 index 00000000..d84069cc --- /dev/null +++ b/tests/tc_ufcs_function_chaining_completion/expected_completion_test6.txt @@ -0,0 +1,2 @@ +identifiers +papaOnly F diff --git a/tests/tc_ufcs_function_chaining_completion/file.d b/tests/tc_ufcs_function_chaining_completion/file.d new file mode 100644 index 00000000..9902f387 --- /dev/null +++ b/tests/tc_ufcs_function_chaining_completion/file.d @@ -0,0 +1,76 @@ +struct Foo {} +Foo foo(Foo f){ + return f; +} + +Foo bar(Foo f){ + return f; +} + +Foo baz(Foo f) { + return f; +} + +Foo qux(Foo f) { + return f; +} + +ref refFoo(ref Foo f) { + return f; +} + +ref refFoo2(ref Foo f) { + return f; +} + +void main() +{ + Foo f; + Foo foo = baz(f.foo().bar()). +} + +void another() { + Foo f; + Foo foo = f.foo().bar().baz(). +} + +void yetAnother() { + Foo f; + Foo foo = f.foo.bar().baz.qux. +} + +void justAnother() { + Foo f; + Foo foo = f.foo().baz().qux() + . +} + +void refTest() { + Foo f; + f. +} + +Mama mamaFoo(Mama m) { + return m; +} + +Mama mamaBar(Mama m, int y) { + return m; +} + +Papa mamaToPapa(Mama m) { + return Papa.init; +} + +Papa papaOnly(Papa p) { + return p; +} + +struct Mama { +} +struct Papa {} + +void moreTesting() { + Mama m; + auto ms = m.mamaFoo.mamaToPapa. +} \ No newline at end of file diff --git a/tests/tc_ufcs_function_chaining_completion/run.sh b/tests/tc_ufcs_function_chaining_completion/run.sh new file mode 100755 index 00000000..09714935 --- /dev/null +++ b/tests/tc_ufcs_function_chaining_completion/run.sh @@ -0,0 +1,20 @@ +set -e +set -u + +../../bin/dcd-client $1 -c265 file.d > actual_completion_test.txt +diff actual_completion_test.txt expected_completion_test.txt --strip-trailing-cr + +../../bin/dcd-client $1 -c325 file.d > actual_completion_test2.txt +diff actual_completion_test2.txt expected_completion_test2.txt --strip-trailing-cr + +../../bin/dcd-client $1 -c388 file.d > actual_completion_test3.txt +diff actual_completion_test3.txt expected_completion_test3.txt --strip-trailing-cr + +../../bin/dcd-client $1 -c454 file.d > actual_completion_test4.txt +diff actual_completion_test4.txt expected_completion_test4.txt --strip-trailing-cr + +../../bin/dcd-client $1 -c486 file.d > actual_completion_test5.txt +diff actual_completion_test5.txt expected_completion_test5.txt --strip-trailing-cr + +../../bin/dcd-client $1 -c751 file.d > actual_completion_test6.txt +diff actual_completion_test6.txt expected_completion_test6.txt --strip-trailing-cr \ No newline at end of file diff --git a/tests/tc_ufcs_overloaded_function_chaining_completion/expected_completion_test.txt b/tests/tc_ufcs_overloaded_function_chaining_completion/expected_completion_test.txt new file mode 100644 index 00000000..7ee43086 --- /dev/null +++ b/tests/tc_ufcs_overloaded_function_chaining_completion/expected_completion_test.txt @@ -0,0 +1,5 @@ +identifiers +showSomething F int showSomething(int x, string message, bool ok, bool lol) stdin 58 int +showSomething F int showSomething(int x, string message, bool ok, float percentage) stdin 130 int +showSomething F void showSomething(int x, string message, bool ok) stdin 5 void +test F int test(int x) stdin 211 int diff --git a/tests/tc_ufcs_overloaded_function_chaining_completion/expected_completion_test2.txt b/tests/tc_ufcs_overloaded_function_chaining_completion/expected_completion_test2.txt new file mode 100644 index 00000000..cb2aba01 --- /dev/null +++ b/tests/tc_ufcs_overloaded_function_chaining_completion/expected_completion_test2.txt @@ -0,0 +1,4 @@ +identifiers +boo F Foo boo(Foo f, ulong id) stdin 325 Foo +boo F Foo boo(Foo f, ulong id, Bar b) stdin 414 Foo +boo F void boo(Foo f, ulong id, Bar b, float num) stdin 367 void diff --git a/tests/tc_ufcs_overloaded_function_chaining_completion/file.d b/tests/tc_ufcs_overloaded_function_chaining_completion/file.d new file mode 100644 index 00000000..a2687884 --- /dev/null +++ b/tests/tc_ufcs_overloaded_function_chaining_completion/file.d @@ -0,0 +1,29 @@ +void showSomething(int x, string message, bool ok) {} +int showSomething(int x, string message, bool ok, bool lol) {return 2;} +int showSomething(int x, string message, bool ok, float percentage) { return 1;} +int test(int x); + +void main() { + int x; + x.showSomething( "my message", true, 2). +} + +struct Foo {} +struct Bar {} + +Foo boo(Foo f, ulong id) { + return f; +} + +void boo(Foo f, ulong id, Bar b, float num) {} + +Foo boo(Foo f, ulong id, Bar b) { + return f; +} + +void doStuff() { + Foo f; + Bar b; + f.boo(2).boo(4, b). +} + diff --git a/tests/tc_ufcs_overloaded_function_chaining_completion/run.sh b/tests/tc_ufcs_overloaded_function_chaining_completion/run.sh new file mode 100755 index 00000000..63ceb5c0 --- /dev/null +++ b/tests/tc_ufcs_overloaded_function_chaining_completion/run.sh @@ -0,0 +1,8 @@ +set -e +set -u + +../../bin/dcd-client $1 --extended -c288 file.d > actual_completion_test.txt +diff actual_completion_test.txt expected_completion_test.txt --strip-trailing-cr + +../../bin/dcd-client $1 --extended -c511 file.d > actual_completion_test2.txt +diff actual_completion_test2.txt expected_completion_test2.txt --strip-trailing-cr \ No newline at end of file