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 }