1 module analysis.constructors;
2 
3 import dparse.ast;
4 import dparse.lexer;
5 import std.stdio;
6 import analysis.base;
7 import analysis.helpers;
8 import dsymbol.scope_ : Scope;
9 
10 class ConstructorCheck : BaseAnalyzer
11 {
12 	alias visit = BaseAnalyzer.visit;
13 
14 	this(string fileName, const(Scope)* sc)
15 	{
16 		super(fileName, sc);
17 	}
18 
19 	override void visit(const ClassDeclaration classDeclaration)
20 	{
21 		immutable bool oldHasDefault = hasDefaultArgConstructor;
22 		immutable bool oldHasNoArg = hasNoArgConstructor;
23 		hasNoArgConstructor = false;
24 		hasDefaultArgConstructor = false;
25 		immutable State prev = state;
26 		state = State.inClass;
27 		classDeclaration.accept(this);
28 		if (hasNoArgConstructor && hasDefaultArgConstructor)
29 		{
30 			addErrorMessage(classDeclaration.name.line,
31 					classDeclaration.name.column, "dscanner.confusing.constructor_args",
32 					"This class has a zero-argument constructor as well as a"
33 					~ " constructor with one default argument. This can be confusing.");
34 		}
35 		hasDefaultArgConstructor = oldHasDefault;
36 		hasNoArgConstructor = oldHasNoArg;
37 		state = prev;
38 	}
39 
40 	override void visit(const StructDeclaration structDeclaration)
41 	{
42 		immutable State prev = state;
43 		state = State.inStruct;
44 		structDeclaration.accept(this);
45 		state = prev;
46 	}
47 
48 	override void visit(const Constructor constructor)
49 	{
50 		final switch (state)
51 		{
52 		case State.inStruct:
53 			if (constructor.parameters.parameters.length == 1
54 					&& constructor.parameters.parameters[0].default_ !is null)
55 			{
56 				addErrorMessage(constructor.line, constructor.column,
57 						"dscanner.confusing.struct_constructor_default_args",
58 						"This struct constructor can never be called with its "
59 						~ "default argument.");
60 			}
61 			break;
62 		case State.inClass:
63 			if (constructor.parameters.parameters.length == 1
64 					&& constructor.parameters.parameters[0].default_ !is null)
65 			{
66 				hasDefaultArgConstructor = true;
67 			}
68 			else if (constructor.parameters.parameters.length == 0)
69 				hasNoArgConstructor = true;
70 			break;
71 		case State.ignoring:
72 			break;
73 		}
74 	}
75 
76 private:
77 
78 	enum State : ubyte
79 	{
80 		ignoring,
81 		inClass,
82 		inStruct
83 	}
84 
85 	State state;
86 
87 	bool hasNoArgConstructor;
88 	bool hasDefaultArgConstructor;
89 }
90 
91 unittest
92 {
93 	import analysis.config : StaticAnalysisConfig;
94 
95 	StaticAnalysisConfig sac;
96 	sac.constructor_check = true;
97 	assertAnalyzerWarnings(q{
98 		class Cat // [warn]: This class has a zero-argument constructor as well as a constructor with one default argument. This can be confusing.
99 		{
100 			this() {}
101 			this(string name = "kittie") {}
102 		}
103 
104 		struct Dog
105 		{
106 			this() {}
107 			this(string name = "doggie") {} // [warn]: This struct constructor can never be called with its default argument.
108 		}
109 	}}, sac);
110 
111 	stderr.writeln("Unittest for ConstructorCheck passed.");
112 }