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