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