1 module analysis.mismatched_args; 2 3 import analysis.base : BaseAnalyzer; 4 import dsymbol.scope_; 5 import dsymbol.symbol; 6 import dparse.ast; 7 import dparse.lexer : tok; 8 import dsymbol.builtin.names; 9 10 /// Checks for mismatched argument and parameter names 11 final class MismatchedArgumentCheck : BaseAnalyzer 12 { 13 /// 14 this(string fileName, const Scope* sc) 15 { 16 super(fileName, sc); 17 } 18 19 override void visit(const FunctionCallExpression fce) 20 { 21 import std.typecons : scoped; 22 import std.algorithm.iteration : each, map; 23 import std.array : array; 24 25 if (fce.arguments is null) 26 return; 27 auto argVisitor = scoped!ArgVisitor; 28 argVisitor.visit(fce.arguments); 29 const istring[] args = argVisitor.args; 30 31 auto identVisitor = scoped!IdentVisitor; 32 identVisitor.visit(fce.unaryExpression); 33 34 const(DSymbol)*[] symbols = resolveSymbol(sc, 35 identVisitor.names.length > 0 ? identVisitor.names : [CONSTRUCTOR_SYMBOL_NAME]); 36 37 static struct ErrorMessage 38 { 39 size_t line; 40 size_t column; 41 string message; 42 } 43 44 ErrorMessage[] messages; 45 bool matched; 46 47 foreach (sym; symbols) 48 { 49 // The cast is a hack because .array() confuses the compiler's overload 50 // resolution code. 51 const(istring)[] params = sym is null ? [] : sym.argNames[].map!(a => cast() a).array(); 52 const ArgMismatch[] mismatches = compareArgsToParams(params, args); 53 if (mismatches.length == 0) 54 matched = true; 55 else 56 { 57 foreach (size_t i, ref const mm; mismatches) 58 { 59 messages ~= ErrorMessage(argVisitor.lines[i], argVisitor.columns[i], 60 createWarningFromMismatch(mm)); 61 } 62 } 63 } 64 65 if (!matched) foreach (m; messages) 66 addErrorMessage(m.line, m.column, KEY, m.message); 67 } 68 69 alias visit = ASTVisitor.visit; 70 71 private: 72 73 enum string KEY = "dscanner.confusing.argument_parameter_mismatch"; 74 } 75 76 final class IdentVisitor : ASTVisitor 77 { 78 override void visit(const IdentifierOrTemplateInstance ioti) 79 { 80 import dsymbol.string_interning : internString; 81 82 if (ioti.identifier != tok!"") 83 names ~= internString(ioti.identifier.text); 84 else 85 names ~= internString(ioti.templateInstance.identifier.text); 86 } 87 88 override void visit(const Arguments) 89 { 90 } 91 92 override void visit(const IndexExpression ie) 93 { 94 if (ie.unaryExpression !is null) 95 visit(ie.unaryExpression); 96 } 97 98 alias visit = ASTVisitor.visit; 99 100 istring[] names; 101 } 102 103 final class ArgVisitor : ASTVisitor 104 { 105 override void visit(const ArgumentList al) 106 { 107 foreach (a; al.items) 108 { 109 auto u = cast(UnaryExpression) a; 110 if (u !is null) 111 visit(u); 112 else 113 { 114 args ~= istring.init; 115 lines ~= size_t.max; 116 columns ~= size_t.max; 117 } 118 } 119 } 120 121 override void visit(const UnaryExpression unary) 122 { 123 import dsymbol.string_interning : internString; 124 125 if (unary.primaryExpression is null) 126 return; 127 if (unary.primaryExpression.identifierOrTemplateInstance is null) 128 return; 129 if (unary.primaryExpression.identifierOrTemplateInstance.identifier == tok!"") 130 return; 131 immutable t = unary.primaryExpression.identifierOrTemplateInstance.identifier; 132 lines ~= t.line; 133 columns ~= t.column; 134 args ~= internString(t.text); 135 } 136 137 alias visit = ASTVisitor.visit; 138 139 size_t[] lines; 140 size_t[] columns; 141 istring[] args; 142 } 143 144 const(DSymbol)*[] resolveSymbol(const Scope* sc, const istring[] symbolChain) 145 { 146 import std.array : empty; 147 148 const(DSymbol)*[] matchingSymbols = sc.getSymbolsByName(symbolChain[0]); 149 if (matchingSymbols.empty) 150 return null; 151 152 foreach (ref symbol; matchingSymbols) 153 { 154 inner: foreach (i; 1 .. symbolChain.length) 155 { 156 if (symbol.kind == CompletionKind.variableName 157 || symbol.kind == CompletionKind.memberVariableName 158 || symbol.kind == CompletionKind.functionName) 159 symbol = symbol.type; 160 if (symbol is null) 161 { 162 symbol = null; 163 break inner; 164 } 165 auto p = symbol.getPartsByName(symbolChain[i]); 166 if (p.empty) 167 { 168 symbol = null; 169 break inner; 170 } 171 symbol = p[0]; 172 } 173 } 174 return matchingSymbols; 175 } 176 177 struct ArgMismatch 178 { 179 size_t argIndex; 180 size_t paramIndex; 181 string name; 182 } 183 184 immutable(ArgMismatch[]) compareArgsToParams(const istring[] params, const istring[] args) pure 185 { 186 import std.exception : assumeUnique; 187 188 if (args.length != params.length) 189 return []; 190 ArgMismatch[] retVal; 191 foreach (i, arg; args) 192 { 193 if (arg is null || arg == params[i]) 194 continue; 195 foreach (j, param; params) 196 if (param == arg) 197 retVal ~= ArgMismatch(i, j, arg); 198 } 199 return assumeUnique(retVal); 200 } 201 202 string createWarningFromMismatch(const ArgMismatch mismatch) pure 203 { 204 import std.format : format; 205 206 return "Argument %d is named '%s', but this is the name of parameter %d".format( 207 mismatch.argIndex + 1, mismatch.name, mismatch.paramIndex + 1); 208 } 209 210 unittest 211 { 212 import dsymbol.string_interning : internString; 213 import std.algorithm.iteration : map; 214 import std.array : array; 215 import std.conv : to; 216 217 { 218 istring[] args = ["a", "b", "c"].map!internString().array(); 219 istring[] params = ["a", "b", "c"].map!internString().array(); 220 immutable res = compareArgsToParams(params, args); 221 assert(res == []); 222 } 223 224 { 225 istring[] args = ["a", "c", "b"].map!internString().array(); 226 istring[] params = ["a", "b", "c"].map!internString().array(); 227 immutable res = compareArgsToParams(params, args); 228 assert(res == [ArgMismatch(1, 2, "c"), ArgMismatch(2, 1, "b")], to!string(res)); 229 } 230 231 { 232 istring[] args = ["a", "c", "b"].map!internString().array(); 233 istring[] params = ["alpha", "bravo", "c"].map!internString().array(); 234 immutable res = compareArgsToParams(params, args); 235 assert(res == [ArgMismatch(1, 2, "c")]); 236 } 237 238 { 239 istring[] args = ["a", "b"].map!internString().array(); 240 istring[] params = [null, "b"].map!internString().array(); 241 immutable res = compareArgsToParams(params, args); 242 assert(res == []); 243 } 244 }