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 }