1 module autowrap.csharp.csharp;
2 
3 import scriptlike : interp, _interp_text;
4 
5 import autowrap.types : isModule, Modules, LibraryName, RootNamespace, OutputFileName;
6 
7 import std.ascii : newline;
8 import std.meta: allSatisfy;
9 import std..string : format;
10 import std.typecons : Tuple;
11 
12 private string[string] returnTypes;
13 private CSharpNamespace[string] namespaces;
14 private csharpRange rangeDef;
15 
16 enum string voidTypeString = "void";
17 enum string stringTypeString = "string";
18 enum string wstringTypeString = "wstring";
19 enum string dstringTypeString = "dstring";
20 enum string boolTypeString = "bool";
21 enum string dateTimeTypeString = "std.datetime.date.DateTime";
22 enum string sysTimeTypeString = "std.datetime.systime.SysTime";
23 enum string dateTypeString = "std.datetime.date.Date";
24 enum string timeOfDayTypeString = "std.datetime.date.TimeOfDay";
25 enum string durationTypeString = "core.time.Duration";
26 enum string uuidTypeString = "UUID";
27 enum string charTypeString = "char";
28 enum string wcharTypeString = "wchar";
29 enum string dcharTypeString = "dchar";
30 enum string ubyteTypeString = "ubyte";
31 enum string byteTypeString = "byte";
32 enum string ushortTypeString = "ushort";
33 enum string shortTypeString = "short";
34 enum string uintTypeString = "uint";
35 enum string intTypeString = "int";
36 enum string ulongTypeString = "ulong";
37 enum string longTypeString = "long";
38 enum string floatTypeString = "float";
39 enum string doubleTypeString = "double";
40 enum string sliceTypeString = "slice";
41 
42 enum string dllImportString = "        [DllImport(\"%1$s\", EntryPoint = \"%2$s\", CallingConvention = CallingConvention.Cdecl)]" ~ newline;
43 enum string externFuncString = "        internal static extern %1$s %2$s(%3$s);" ~ newline;
44 
45 enum int fileReservationSize = 33_554_432;
46 enum int aggregateReservationSize = 32_768;
47 
48 //Class used for language built-in reference semantics.
49 private final class CSharpNamespace {
50     public string namespace;
51     public string functions;
52     public CSharpAggregate[string] aggregates;
53 
54     public this(string ns) {
55         namespace = ns;
56         functions = string.init;
57     }
58 
59     public override string toString() {
60         string ret = "namespace " ~ namespace ~ " {
61     using System;
62     using System.CodeDom.Compiler;
63     using System.Collections.Generic;
64     using System.Linq;
65     using System.Reflection;
66     using System.Runtime.InteropServices;
67     using Autowrap;
68 
69     // This class aggregates all wrapped free (global) D functions
70     public static class Functions {
71 " ~ functions ~ "
72     }" ~ newline ~ newline;
73 
74         foreach(agg; aggregates.byValue()) {
75             ret ~= agg.toString();
76         }
77 
78         ret ~= "}" ~ newline;
79 
80         return ret;
81     }
82 }
83 
84 //Class used for language built-in reference semantics.
85 private final class CSharpAggregate {
86     public string name;
87     public bool isStruct;
88     public string constructors;
89     public string functions;
90     public string methods;
91     public string properties;
92 
93     public this (string name, bool isStruct) {
94         this.name = name;
95         this.isStruct = isStruct;
96         this.constructors = string.init;
97         this.functions = string.init;
98         this.methods = string.init;
99         this.properties = string.init;
100     }
101 
102     public override string toString() {
103         import autowrap.csharp.common : camelToPascalCase;
104 
105         char[] ret;
106         ret.reserve(aggregateReservationSize);
107         ret ~= "    [GeneratedCodeAttribute(\"Autowrap\", \"1.0.0.0\")]" ~ newline;
108         if (isStruct) {
109             ret ~= "    [StructLayout(LayoutKind.Sequential)]" ~ newline;
110             ret ~= mixin(interp!"    public struct ${camelToPascalCase(this.name)} {${newline}");
111         } else {
112             ret ~= mixin(interp!"    public class ${camelToPascalCase(this.name)} : DLangObject {${newline}");
113             ret ~= mixin(interp!"        public static implicit operator IntPtr(${camelToPascalCase(this.name)} ret) { return ret.DLangPointer; }${newline}");
114             ret ~= mixin(interp!"        public static implicit operator ${camelToPascalCase(this.name)}(IntPtr ret) { return new ${camelToPascalCase(this.name)}(ret); }${newline}");
115         }
116         if (functions != string.init) ret ~= functions ~ newline;
117         if (constructors != string.init) ret ~= constructors ~ newline;
118         if (methods != string.init) ret ~= methods ~ newline;
119         if (properties != string.init) ret ~= properties;
120         ret ~= "    }" ~ newline ~ newline;
121         return cast(immutable)ret;
122     }
123 }
124 
125 public struct csharpRange {
126     public string constructors = string.init;
127     public string enumerators = string.init;
128     public string functions = string.init;
129     public string getters = string.init;
130     public string setters = string.init;
131     public string appendValues = string.init;
132     public string appendArrays = string.init;
133     public string sliceEnd = string.init;
134     public string sliceRange = string.init;
135 
136     public string toString() {
137         immutable string boilerplate = import("Ranges.cs");
138         return boilerplate.format(functions, constructors, getters, setters, sliceEnd, sliceRange, appendValues, appendArrays, enumerators);
139     }
140 }
141 
142 public string generateCSharp(Modules...)(LibraryName libraryName, RootNamespace rootNamespace) if(allSatisfy!(isModule, Modules)) {
143     import core.time : Duration;
144     import autowrap.csharp.common : isDateTimeType, verifySupported;
145     import autowrap.reflection : AllAggregates;
146 
147     generateSliceBoilerplate(libraryName.value);
148 
149     static foreach(Agg; AllAggregates!Modules)
150     {
151         static if(verifySupported!Agg && !isDateTimeType!Agg && !is(Agg == enum))
152         {
153             generateRangeDef!Agg(libraryName.value);
154             generateConstructors!Agg(libraryName.value);
155             generateMethods!Agg(libraryName.value);
156             generateProperties!Agg(libraryName.value);
157             generateFields!Agg(libraryName.value);
158         }
159     }
160 
161     generateFunctions!Modules(libraryName.value);
162 
163     string ret;
164     enum banner = `// Auto generated by autowrap. Please do not edit by hand.` ~ "\n\n";
165     ret ~= banner;
166     foreach(csns; namespaces.byValue()) {
167         ret ~= csns.toString();
168     }
169 
170     ret ~= newline ~ writeCSharpBoilerplate(libraryName.value, rootNamespace.value);
171 
172     return ret;
173 }
174 
175 private void generateConstructors(T)(string libraryName) if (is(T == class) || is(T == struct)) {
176     import autowrap.csharp.common : getDLangInterfaceName, numDefaultArgs, verifySupported;
177 
178     import std.algorithm : among;
179     import std.conv : to;
180     import std.format : format;
181     import std.meta: AliasSeq, Filter;
182     import std.traits : moduleName, fullyQualifiedName, hasMember, Parameters, ParameterIdentifierTuple;
183 
184     alias fqn = fullyQualifiedName!T;
185     const string aggName = __traits(identifier, T);
186     CSharpAggregate csagg = getAggregate(getCSharpName(moduleName!T), getCSharpName(aggName), !is(T == class));
187 
188     //Generate constructor methods
189     static if(hasMember!(T, "__ctor") && __traits(getProtection, __traits(getMember, T, "__ctor")).among("export", "public"))
190     {
191         foreach(i, c; __traits(getOverloads, T, "__ctor"))
192         {
193             if (__traits(getProtection, c).among("export", "public"))
194             {
195                 alias paramNames = ParameterIdentifierTuple!c;
196                 alias ParamTypes = Parameters!c;
197 
198                 static if(Filter!(verifySupported, ParamTypes).length != ParamTypes.length)
199                     continue;
200 
201                 static foreach(nda; 0 .. numDefaultArgs!c + 1)
202                 {{
203                     enum numParams = ParamTypes.length - nda;
204 
205                     enum interfaceName = format("%s%s_%s", getDLangInterfaceName(fqn, "__ctor"), i, numParams);
206                     enum methodInterfaceName = format("%s%s_%s", getCSharpMethodInterfaceName(aggName, "__ctor"), i, numParams);
207 
208                     string ctor = dllImportString.format(libraryName, interfaceName);
209                     ctor ~= mixin(interp!"        private static extern ${getDLangReturnType!(T, T)()} dlang_${methodInterfaceName}(");
210 
211                     static foreach(pc; 0 .. numParams)
212                     {
213                         if (is(ParamTypes[pc] == class))
214                             ctor ~= mixin(interp!"IntPtr ${paramNames[pc]}, ");
215                         else
216                             ctor ~= mixin(interp!"${is(ParamTypes[pc] == bool) ? \"[MarshalAs(UnmanagedType.Bool)]\" : string.init}${getDLangInterfaceType!(ParamTypes[pc], T)()} ${paramNames[pc]}, ");
217                     }
218 
219                     if (numParams != 0)
220                         ctor = ctor[0 .. $ - 2];
221 
222                     ctor ~= ");" ~ newline;
223                     ctor ~= mixin(interp!"        public ${getCSharpName(aggName)}(");
224                     static foreach(pc; 0 .. numParams)
225                         ctor ~= mixin(interp!"${getCSharpInterfaceType(fullyQualifiedName!(ParamTypes[pc]))} ${paramNames[pc]}, ");
226 
227                     if (numParams != 0)
228                         ctor = ctor[0 .. $ - 2];
229 
230                     static if (is(T == class))
231                         ctor ~= mixin(interp!") : base(dlang_${methodInterfaceName}(");
232                     else static if(is(T == struct))
233                     {
234                         ctor ~= ") {" ~ newline;
235                         ctor ~= mixin(interp!"            this = dlang_${methodInterfaceName}(");
236                     }
237                     else
238                         static assert(false, "Somehow, this type has a constructor even though it is neither a class nor a struct: " ~ T.stringof);
239 
240                     static foreach(pc; 0 .. numParams)
241                     {
242                         static if (is(ParamTypes[pc] == string))
243                             ctor ~= mixin(interp!"SharedFunctions.CreateString(${paramNames[pc]}), ");
244                         else static if (is(ParamTypes[pc] == wstring))
245                             ctor ~= mixin(interp!"SharedFunctions.CreateWString(${paramNames[pc]}), ");
246                         else static if (is(ParamTypes[pc] == dstring))
247                             ctor ~= mixin(interp!"SharedFunctions.CreateDString(${paramNames[pc]}), ");
248                         else
249                             ctor ~= mixin(interp!"${paramNames[pc]}, ");
250                     }
251 
252                     if (numParams != 0)
253                         ctor = ctor[0 .. $ - 2];
254 
255                     ctor ~= ")";
256 
257                     static if (is(T == class))
258                         ctor ~= ") { }" ~ newline;
259                     else
260                     {
261                         ctor ~= ";" ~ newline;
262                         ctor ~= "        }" ~ newline;
263                     }
264 
265                     csagg.constructors ~= ctor;
266                 }}
267             }
268         }
269     }
270 
271     if (is(T == class))
272         csagg.constructors ~= mixin(interp!"        internal ${getCSharpName(aggName)}(IntPtr ptr) : base(ptr) { }${newline}");
273 }
274 
275 private void generateMethods(T)(string libraryName)
276     if (is(T == class) || is(T == interface) || is(T == struct))
277 {
278     import autowrap.csharp.common : getDLangInterfaceName, numDefaultArgs, verifySupported;
279 
280     import std.algorithm.comparison : among;
281     import std.conv : to;
282     import std.format : format;
283     import std.meta : AliasSeq, Filter;
284     import std.traits : moduleName, isDynamicArray, fullyQualifiedName, isFunction, functionAttributes, FunctionAttribute,
285                         ReturnType, Parameters, ParameterIdentifierTuple;
286 
287     alias fqn = fullyQualifiedName!T;
288     const string aggName = __traits(identifier, T);
289     CSharpAggregate csagg = getAggregate(getCSharpName(moduleName!T), getCSharpName(aggName), !is(T == class));
290 
291     foreach(m; __traits(allMembers, T))
292     {
293         static if (!m.among("__ctor", "toHash", "opEquals", "opCmp", "factory") &&
294                    is(typeof(__traits(getMember, T, m))))
295         {
296             enum methodName = getCSMemberName(aggName, cast(string)m);
297 
298             foreach(oc, mo; __traits(getOverloads, T, m))
299             {
300                 static if(isFunction!mo)
301                 {
302                     static foreach(nda; 0 .. numDefaultArgs!mo + 1)
303                     {{
304                         alias RT = ReturnType!mo;
305                         alias ParamTypes = Parameters!mo;
306                         alias Types = AliasSeq!(RT, ParamTypes);
307 
308                         static if(Filter!(verifySupported, Types).length != Types.length)
309                             continue;
310                         else
311                         {
312                             enum numParams = ParamTypes.length - nda;
313                             alias paramNames = ParameterIdentifierTuple!mo;
314 
315                             enum dlangInterfaceName = format("%s%s_%s", getDLangInterfaceName(fqn, m), oc, numParams);
316                             enum methodInterfaceName = format("dlang_%s", getCSharpMethodInterfaceName(aggName, cast(string)m));
317 
318                             string exp = dllImportString.format(libraryName, dlangInterfaceName);
319                             exp ~= "        private static extern ";
320 
321                             if (!is(RT == void))
322                                 exp ~= getDLangReturnType!(RT, T)();
323                             else
324                                 exp ~= "return_void_error";
325 
326                             exp ~= mixin(interp!" ${methodInterfaceName}(");
327 
328                             if (is(T == struct))
329                                 exp ~= mixin(interp!"ref ${getDLangInterfaceType!(T, T)()} __obj__, ");
330                             else if (is(T == class) || is(T == interface))
331                                 exp ~= "IntPtr __obj__, ";
332 
333                             static foreach(pc; 0 .. numParams)
334                             {
335                                 if (is(ParamTypes[pc] == class))
336                                     exp ~= mixin(interp!"IntPtr ${paramNames[pc]}, ");
337                                 else
338                                     exp ~= mixin(interp!"${getDLangInterfaceType!(ParamTypes[pc], T)()} ${paramNames[pc]}, ");
339                             }
340 
341                             exp = exp[0 .. $-2];
342                             exp ~= ");" ~ newline;
343 
344                             if ((functionAttributes!mo & FunctionAttribute.property) == 0)
345                             {
346                                 enum returnTypeStr = fullyQualifiedName!RT;
347 
348                                 exp ~= mixin(interp!`        public ${methodName == "ToString" ? "override " : ""}${getCSharpInterfaceType(returnTypeStr)} ${methodName}(`);
349                                 static foreach(pc; 0 .. numParams)
350                                 {
351                                     if (is(ParamTypes[pc] == string) || is(ParamTypes[pc] == wstring) || is(ParamTypes[pc] == dstring))
352                                         exp ~= mixin(interp!"${getCSharpInterfaceType(fullyQualifiedName!(ParamTypes[pc]))} ${paramNames[pc]}, ");
353                                     else if (isDynamicArray!(ParamTypes[pc]))
354                                         exp ~= mixin(interp!"Range<${getCSharpInterfaceType(fullyQualifiedName!(ParamTypes[pc]))}> ${paramNames[pc]}, ");
355                                     else
356                                         exp ~= mixin(interp!"${getCSharpInterfaceType(fullyQualifiedName!(ParamTypes[pc]))} ${paramNames[pc]}, ");
357                                 }
358 
359                                 if (numParams != 0)
360                                     exp = exp[0 .. $-2];
361 
362                                 exp ~= ") {" ~ newline;
363                                 exp ~= mixin(interp!`            var dlang_ret = ${methodInterfaceName}(${is(T == struct) ? "ref " : ""}this, `);
364 
365                                 static foreach(pc; 0 .. numParams)
366                                 {
367                                     static if (is(ParamTypes[pc] == string))
368                                         exp ~= mixin(interp!"SharedFunctions.CreateString(${paramNames[pc]}), ");
369                                     else static if (is(ParamTypes[pc] == wstring))
370                                         exp ~= mixin(interp!"SharedFunctions.CreateWstring(${paramNames[pc]}), ");
371                                     else static if (is(ParamTypes[pc] == dstring))
372                                         exp ~= mixin(interp!"SharedFunctions.CreateDstring(${paramNames[pc]}), ");
373                                     else
374                                         exp ~= paramNames[pc] ~ ", ";
375                                 }
376 
377                                 exp = exp[0 .. $-2];
378                                 exp ~= ");" ~ newline;
379 
380                                 if (!is(RT == void))
381                                 {
382                                     if (is(RT == string))
383                                         exp ~= "            return SharedFunctions.SliceToString(dlang_ret, DStringType._string);" ~ newline;
384                                     else if (is(RT == wstring))
385                                         exp ~= "            return SharedFunctions.SliceToString(dlang_ret, DStringType._wstring);" ~ newline;
386                                     else if (is(RT == dstring))
387                                         exp ~= "            return SharedFunctions.SliceToString(dlang_ret, DStringType._dstring);" ~ newline;
388                                     else
389                                         exp ~= "            return dlang_ret;" ~ newline;
390                                 }
391 
392                                 exp ~= "        }" ~ newline;
393                             }
394 
395                             csagg.methods ~= exp;
396                         }
397                     }}
398                 }
399             }
400         }
401     }
402 }
403 
404 private void generateProperties(T)(string libraryName)
405     if (is(T == class) || is(T == interface) || is(T == struct))
406 {
407     import autowrap.csharp.common : verifySupported;
408 
409     import std.algorithm.comparison : among;
410     import std.format : format;
411     import std.meta : AliasSeq, Filter, staticIndexOf;
412     import std.traits : moduleName, isDynamicArray, fullyQualifiedName;
413 
414     enum fqn = fullyQualifiedName!T;
415     enum aggName = __traits(identifier, T);
416     CSharpAggregate csagg = getAggregate(getCSharpName(moduleName!T), getCSharpName(aggName), !is(T == class));
417 
418     foreach(m; __traits(allMembers, T))
419     {
420         static if (!m.among("__ctor", "toHash", "opEquals", "opCmp", "factory") &&
421                    is(typeof(__traits(getMember, T, m))))
422         {
423             enum methodName = getCSMemberName(aggName, cast(string)m);
424 
425             alias GType = GetterPropertyType!(T, m);
426             alias STypes = SetterPropertyTypes!(T, m);
427 
428             static if(GType.length == 1)
429             {
430                 alias PT = GType[0];
431                 enum getterInterfaceName = format("dlang_%s", getCSharpMethodInterfaceName(aggName, cast(string)m));
432 
433                 static if(staticIndexOf!(PT, STypes) != -1)
434                     enum setterInterfaceName = format("dlang_%s", getCSharpMethodInterfaceName(aggName, cast(string)m));
435             }
436             else static if(STypes.length == 1)
437             {
438                 alias PT = STypes[0];
439                 enum setterInterfaceName = format("dlang_%s", getCSharpMethodInterfaceName(aggName, cast(string)m));
440             }
441 
442             static if(is(PT) && verifySupported!PT)
443             {
444                 static if (is(PT == string) || is(PT == wstring) || is(PT == dstring))
445                     string prop = mixin(interp!"        public string ${methodName} { ");
446                 else static if (isDynamicArray!PT)
447                     string prop = mixin(interp!"        public Range<${getCSharpInterfaceType(fullyQualifiedName!PT)}> ${methodName} { ");
448                 else
449                     string prop = mixin(interp!"        public ${getCSharpInterfaceType(fullyQualifiedName!PT)} ${methodName} { ");
450 
451                 static if (is(typeof(getterInterfaceName)))
452                 {
453                     static if (is(PT == string))
454                         prop ~= mixin(interp!`get => SharedFunctions.SliceToString(${getterInterfaceName}(${is(T == class) ? "" : "ref "}this), DStringType._string);`);
455                     else static if (is(PT == wstring))
456                         prop ~= mixin(interp!`get => SharedFunctions.SliceToString(${getterInterfaceName}(${is(T == class) ? "" : "ref "}this), DStringType._wstring);`);
457                     else static if (is(PT == dstring))
458                         prop ~= mixin(interp!`get => SharedFunctions.SliceToString(${getterInterfaceName}(${is(T == class) ? "" : "ref "}this), DStringType._dstring);`);
459                     else static if (is(PT == string[]))
460                         prop ~= mixin(interp!`get => new Range<string>(${getterInterfaceName}(${is(T == class) ? "" : "ref "}this), DStringType._string); `);
461                     else static if (is(PT == wstring[]))
462                         prop ~= mixin(interp!`get => new Range<string>(${getterInterfaceName}(${is(T == class) ? "" : "ref "}this), DStringType._wstring); `);
463                     else static if (is(PT == dstring[]))
464                         prop ~= mixin(interp!`get => new Range<string>(${getterInterfaceName}(${is(T == class) ? "" : "ref "}this), DStringType._dstring); `);
465                     else static if (isDynamicArray!PT)
466                         prop ~= mixin(interp!`get => new Range<${getCSharpInterfaceType(fullyQualifiedName!PT)}>(${getterInterfaceName}(${is(T == class) ? "" : "ref "}this), DStringType.None); `);
467                     else static if (is(PT == class))
468                         prop ~= mixin(interp!`get => new ${getCSharpInterfaceType(fullyQualifiedName!PT)}(${getterInterfaceName}(${is(T == class) ? "" : "ref "}this)); `);
469                     else
470                         prop ~= mixin(interp!`get => ${getterInterfaceName}(${is(T == class) ? "" : "ref "}this); `);
471                 }
472 
473                 static if (is(typeof(setterInterfaceName)))
474                 {
475                     static if (is(PT == string))
476                         prop ~= mixin(interp!`set => ${setterInterfaceName}(this, SharedFunctions.CreateString(value));`);
477                     else static if (is(PT == wstring))
478                         prop ~= mixin(interp!`set => ${setterInterfaceName}(this, SharedFunctions.CreateWstring(value));`);
479                     else static if (is(PT == dstring))
480                         prop ~= mixin(interp!`set => ${setterInterfaceName}(this, SharedFunctions.CreateDstring(value));`);
481                     else static if (is(PT == string[]))
482                         prop ~= mixin(interp!`set => ${setterInterfaceName}(${is(T == class) ? "" : "ref "}this, value.ToSlice(DStringType._string)); `);
483                     else static if (is(PT == wstring[]))
484                         prop ~= mixin(interp!`set => ${setterInterfaceName}(${is(T == class) ? "" : "ref "}this, value.ToSlice(DStringType._wstring)); `);
485                     else static if (is(PT == dstring[]))
486                         prop ~= mixin(interp!`set => ${setterInterfaceName}(${is(T == class) ? "" : "ref "}this, value.ToSlice(DStringType._dstring)); `);
487                     else static if (isDynamicArray!PT)
488                         prop ~= mixin(interp!`set => ${setterInterfaceName}(${is(T == class) ? "" : "ref "}this, value.ToSlice()); `);
489                     else
490                         prop ~= mixin(interp!`set => ${setterInterfaceName}(${is(T == class) ? "" : "ref "}this, value); `);
491                 }
492 
493                 prop ~= "}" ~ newline;
494                 csagg.properties ~= prop;
495             }
496         }
497     }
498 }
499 
500 private void generateFields(T)(string libraryName) if (is(T == class) || is(T == interface) || is(T == struct)) {
501     import autowrap.csharp.common : getDLangInterfaceName, verifySupported;
502     import std.traits : moduleName, isDynamicArray, fullyQualifiedName, Fields, FieldNameTuple;
503 
504     alias fqn = fullyQualifiedName!T;
505     const string aggName = __traits(identifier, T);
506     CSharpAggregate csagg = getAggregate(getCSharpName(moduleName!T), getCSharpName(aggName), !is(T == class));
507 
508     alias FieldTypes = Fields!T;
509     alias fieldNames = FieldNameTuple!T;
510 
511     if (is(T == class) || is(T == interface))
512     {
513         static foreach(fc; 0..FieldTypes.length)
514         {{
515             alias FT = FieldTypes[fc];
516             static if(verifySupported!FT)
517             {
518                 alias fn = fieldNames[fc];
519                 static if (is(typeof(__traits(getMember, T, fn))))
520                 {
521                     csagg.properties ~= dllImportString.format(libraryName, getDLangInterfaceName(fqn, fn ~ "_get"));
522                     csagg.properties ~= mixin(interp!"        private static extern ${getDLangReturnType!(FT, T)} dlang_${fn}_get(IntPtr ptr);${newline}");
523                     if (is(FT == bool)) {
524                         csagg.properties ~= dllImportString.format(libraryName, getDLangInterfaceName(fqn, fn ~ "_set"));
525                         csagg.properties ~= mixin(interp!"        private static extern void dlang_${fn}_set(IntPtr ptr, [MarshalAs(UnmanagedType.Bool)] ${getDLangInterfaceType!(FT, T)()} value);${newline}");
526                     } else if (is(FT == class)) {
527                         csagg.properties ~= dllImportString.format(libraryName, getDLangInterfaceName(fqn, fn ~ "_set"));
528                         csagg.properties ~= mixin(interp!"        private static extern void dlang_${fn}_set(IntPtr ptr, IntPtr value);${newline}");
529                     } else {
530                         csagg.properties ~= dllImportString.format(libraryName, getDLangInterfaceName(fqn, fn ~ "_set"));
531                         csagg.properties ~= mixin(interp!"        private static extern void dlang_${fn}_set(IntPtr ptr, ${getDLangInterfaceType!(FT, T)()} value);${newline}");
532                     }
533 
534                     string memberName = getCSMemberName(aggName, fn);
535 
536                     if (is(FT == string)) {
537                         csagg.properties ~= mixin(interp!"        public string ${memberName} { get => SharedFunctions.SliceToString(dlang_${fn}_get(this), DStringType._string); set => dlang_${fn}_set(this, SharedFunctions.CreateString(value)); }${newline}");
538                     } else if (is(FT == wstring)) {
539                         csagg.properties ~= mixin(interp!"        public string ${memberName} { get => SharedFunctions.SliceToString(dlang_${fn}_get(this), DStringType._wstring); set => dlang_${fn}_set(this, SharedFunctions.CreateWstring(value)); }${newline}");
540                     } else if (is(FT == dstring)) {
541                         csagg.properties ~= mixin(interp!"        public string ${memberName} { get => SharedFunctions.SliceToString(dlang_${fn}_get(this), DStringType._dstring); set => dlang_${fn}_set(this, SharedFunctions.CreateDstring(value)); }${newline}");
542                     } else if (is(FT == string[])) {
543                         csagg.properties ~= mixin(interp!"        public Range<string> ${memberName} { get => new Range<string>(dlang_${fn}_get(this), DStringType._string); set => dlang_${fn}_set(this, value.ToSlice(DStringType._string)); }${newline}");
544                     } else if (is(FT == wstring[])) {
545                         csagg.properties ~= mixin(interp!"        public Range<string> ${memberName} { get => new Range<string>(dlang_${fn}_get(this), DStringType._wstring); set => dlang_${fn}_set(this, value.ToSlice(DStringType._wstring)); }${newline}");
546                     } else if (is(FT == dstring[])) {
547                         csagg.properties ~= mixin(interp!"        public Range<string> ${memberName} { get => new Range<string>(dlang_${fn}_get(this), DStringType._dstring); set => dlang_${fn}_set(this, value.ToSlice(DStringType._dstring)); }${newline}");
548                     } else if (isDynamicArray!FT) {
549                         csagg.properties ~= mixin(interp!"        public Range<${getCSharpInterfaceType(fullyQualifiedName!(FT))}> ${memberName} { get => new Range<${getCSharpInterfaceType(fullyQualifiedName!(FT))}>(dlang_${fn}_get(this), DStringType.None); set => dlang_${fn}_set(this, value.ToSlice()); }${newline}");
550                     } else if (is(FT == class)) {
551                         csagg.properties ~= mixin(interp!"        public ${getCSharpInterfaceType(fullyQualifiedName!(FT))} ${memberName} { get => new ${getCSharpInterfaceType(fullyQualifiedName!(FT))}(dlang_${fn}_get(this)); set => dlang_${fn}_set(this, value); }${newline}");
552                     } else {
553                         csagg.properties ~= mixin(interp!"        public ${getCSharpInterfaceType(fullyQualifiedName!(FT))} ${memberName} { get => dlang_${fn}_get(this); set => dlang_${fn}_set(this, value); }${newline}");
554                     }
555                 }
556             }
557         }}
558     }
559     else if(is(T == struct))
560     {
561         static foreach(fc; 0..FieldTypes.length)
562         {{
563             alias FT = FieldTypes[fc];
564             static if(verifySupported!FT)
565             {
566                 alias fn = fieldNames[fc];
567                 static if (is(typeof(__traits(getMember, T, fn))))
568                 {
569                     auto nameTuple = getCSFieldNameTuple(aggName, fn);
570                     auto pubName = nameTuple.public_;
571                     auto privName = nameTuple.private_;
572 
573                     if (isDynamicArray!FT) {
574                         csagg.properties ~= mixin(interp!"        private slice ${privName};${newline}");
575                         if (is(FT == string)) {
576                             csagg.properties ~= mixin(interp!"        public string ${pubName} { get => SharedFunctions.SliceToString(${privName}, DStringType._string); set => ${privName} = SharedFunctions.CreateString(value); }${newline}");
577                         } else if (is(FT == wstring)) {
578                             csagg.properties ~= mixin(interp!"        public string ${pubName} { get => SharedFunctions.SliceToString(${privName}, DStringType._wstring); set => ${privName} = SharedFunctions.CreateWstring(value); }${newline}");
579                         } else if (is(FT == dstring)) {
580                             csagg.properties ~= mixin(interp!"        public string ${pubName} { get => SharedFunctions.SliceToString(${privName}, DStringType._dstring); set => ${privName} = SharedFunctions.CreateDstring(value); }${newline}");
581                         } else if (is(FT == string[])) {
582                             csagg.properties ~= mixin(interp!"        public Range<string> ${pubName} { get => new Range<string>(_${fn}, DStringType._string); set => _${fn} = value.ToSlice(DStringType._string); }${newline}");
583                         } else if (is(FT == wstring[])) {
584                             csagg.properties ~= mixin(interp!"        public Range<string> ${pubName} { get => new Range<string>(_${fn}, DStringType._wstring); set => _${fn} = value.ToSlice(DStringType._wstring); }${newline}");
585                         } else if (is(FT == dstring[])) {
586                             csagg.properties ~= mixin(interp!"        public Range<string> ${pubName} { get => new Range<string>(_${fn}, DStringType._dstring); set => _${fn} = value.ToSlice(DStringType._dstring); }${newline}");
587                         } else {
588                             csagg.properties ~= mixin(interp!"        public Range<${getCSharpInterfaceType(fullyQualifiedName!(FT))}> ${pubName} { get => new Range<${getCSharpInterfaceType(fullyQualifiedName!(FT))}>(_${fn}, DStringType.None); set => _${fn} = value.ToSlice(); }${newline}");
589                         }
590                     } else if (is(FT == bool)) {
591                         csagg.properties ~= mixin(interp!"        [MarshalAs(UnmanagedType.U1)] public ${getDLangInterfaceType!(FT, T)()} ${pubName};${newline}");
592                     } else if (is(FT == class)) {
593                         csagg.properties ~= mixin(interp!"        private IntPtr ${privName};${newline}");
594                         csagg.properties ~= mixin(interp!"        public ${getCSharpInterfaceType(fullyQualifiedName!(FT))} ${pubName} { get => new ${getCSharpInterfaceType(fullyQualifiedName!(FT))}(_${fn}); set => _${fn} = value); }${newline}");
595                     } else {
596                         csagg.properties ~= mixin(interp!"        public ${getDLangInterfaceType!(FT, T)()} ${pubName};${newline}");
597                     }
598                 }
599             }
600         }}
601     }
602 }
603 
604 private void generateFunctions(Modules...)(string libraryName)
605     if(allSatisfy!(isModule, Modules))
606 {
607     import autowrap.csharp.common : getDLangInterfaceName, numDefaultArgs, verifySupported;
608     import autowrap.reflection: AllFunctions;
609 
610     import std.format : format;
611     import std.meta : AliasSeq, Filter;
612     import std.traits : isDynamicArray, fullyQualifiedName, ReturnType,
613         Parameters, ParameterIdentifierTuple, moduleName;
614 
615     foreach(func; AllFunctions!Modules)
616     {
617         foreach(oc, overload; __traits(getOverloads, func.parent, func.identifier))
618         {
619             alias RT = ReturnType!overload;
620             alias ParamTypes = Parameters!overload;
621             alias Types = AliasSeq!(RT, ParamTypes);
622 
623             static if(Filter!(verifySupported, Types).length != Types.length)
624                 continue;
625             else
626             {
627                 static foreach(nda; 0 .. numDefaultArgs!overload + 1)
628                 {{
629                     enum numParams = ParamTypes.length - nda;
630                     enum interfaceName = format("%s%s_%s", getDLangInterfaceName(moduleName!(func.parent), null, func.identifier), oc, numParams);
631                     enum methodName = getCSharpMethodInterfaceName(null, func.identifier);
632                     enum methodInterfaceName = format("dlang_%s", methodName);
633 
634                     enum returnTypeStr = getCSharpInterfaceType(fullyQualifiedName!RT);
635                     alias paramNames = ParameterIdentifierTuple!overload;
636 
637                     string retType = getDLangReturnType!RT();
638                     string funcStr = dllImportString.format(libraryName, interfaceName) ~ newline;
639                     funcStr ~= mixin(interp!"        private static extern ${retType} ${methodInterfaceName}(");
640                     static foreach(pc; 0 .. numParams)
641                         funcStr ~= mixin(interp!"${is(ParamTypes[pc] == bool) ? \"[MarshalAs(UnmanagedType.Bool)]\" : string.init}${getDLangInterfaceType!(ParamTypes[pc])()} ${paramNames[pc]}, ");
642 
643                     if (numParams != 0)
644                         funcStr = funcStr[0 .. $ - 2];
645 
646                     funcStr ~= ");" ~ newline;
647 
648                     if (is(RT == string) || is(RT == wstring) || is(RT == dstring))
649                         funcStr ~= mixin(interp!"        public static string ${methodName}(");
650                     else if (isDynamicArray!RT)
651                         funcStr ~= mixin(interp!"        public static Range<${returnTypeStr}> ${methodName}(");
652                     else
653                         funcStr ~= mixin(interp!"        public static ${returnTypeStr} ${methodName}(");
654 
655                     static foreach(pc; 0 .. numParams)
656                     {
657                         if (is(ParamTypes[pc] == string) || is(ParamTypes[pc] == wstring) || is(ParamTypes[pc] == dstring))
658                             funcStr ~= mixin(interp!"${getCSharpInterfaceType(fullyQualifiedName!(ParamTypes[pc]))} ${paramNames[pc]}, ");
659                         else if (isDynamicArray!(ParamTypes[pc]))
660                             funcStr ~= mixin(interp!"Range<${getCSharpInterfaceType(fullyQualifiedName!(ParamTypes[pc]))}> ${paramNames[pc]}, ");
661                         else
662                             funcStr ~= mixin(interp!"${getCSharpInterfaceType(fullyQualifiedName!(ParamTypes[pc]))} ${paramNames[pc]}, ");
663                     }
664 
665                     if (numParams != 0)
666                         funcStr = funcStr[0 .. $ - 2];
667 
668                     funcStr ~= ") {" ~ newline;
669                     funcStr ~= mixin(interp!"            var dlang_ret = ${methodInterfaceName}(");
670 
671                     static foreach(pc; 0 .. numParams)
672                     {
673                         if (is(ParamTypes[pc] == string))
674                             funcStr ~= mixin(interp!"SharedFunctions.CreateString(${paramNames[pc]}), ");
675                         else if (is(ParamTypes[pc] == wstring))
676                             funcStr ~= mixin(interp!"SharedFunctions.CreateWString(${paramNames[pc]}), ");
677                         else if (is(ParamTypes[pc] == dstring))
678                             funcStr ~= mixin(interp!"SharedFunctions.CreateDString(${paramNames[pc]}), ");
679                         else if (isDynamicArray!(ParamTypes[pc]))
680                             funcStr ~= mixin(interp!"${paramNames[pc]}.ToSlice(), ");
681                         else
682                             funcStr ~= mixin(interp!"${paramNames[pc]}, ");
683                     }
684 
685                     if (numParams != 0)
686                         funcStr = funcStr[0 .. $ - 2];
687 
688                     funcStr ~= ");" ~ newline;
689 
690                     if (is(RT == void))
691                         funcStr ~= "            dlang_ret.EnsureValid();" ~ newline;
692                     else
693                     {
694                         if (is(RT == string))
695                             funcStr ~= "            return SharedFunctions.SliceToString(dlang_ret, DStringType._string);" ~ newline;
696                         else if (is(RT == wstring))
697                             funcStr ~= "            return SharedFunctions.SliceToString(dlang_ret, DStringType._wstring);" ~ newline;
698                         else if (is(RT == dstring))
699                             funcStr ~= "            return SharedFunctions.SliceToString(dlang_ret, DStringType._dstring);" ~ newline;
700                         else if (is(RT == string[]))
701                             funcStr ~= mixin(interp!"            return new Range<string>(dlang_ret, DStringType._string);${newline}");
702                         else if (is(RT == wstring[]))
703                             funcStr ~= mixin(interp!"            return new Range<string>(dlang_ret, DStringType._wstring);${newline}");
704                         else if (is(RT == dstring[]))
705                             funcStr ~= mixin(interp!"            return new Range<string>(dlang_ret, DStringType._dstring);${newline}");
706                         else if (isDynamicArray!RT)
707                             funcStr ~= mixin(interp!"            return new Range<${returnTypeStr}>(dlang_ret, DStringType.None);${newline}");
708                         else
709                             funcStr ~= "            return dlang_ret;" ~ newline;
710                     }
711 
712                     funcStr ~= "        }" ~ newline;
713 
714                     getNamespace(getCSharpName(moduleName!(func.parent))).functions ~= funcStr;
715                 }}
716             }
717         }
718     }
719 }
720 
721 private string getDLangInterfaceType(T, Parent...)()
722     if(Parent.length <= 1)
723 {
724     import std.traits : fullyQualifiedName, isBuiltinType;
725 
726     static if(!isBuiltinType!T && Parent.length == 1)
727     {
728         static if(is(T == Parent[0]))
729             enum typeName = __traits(identifier, T);
730         else
731             enum typeName = fullyQualifiedName!T;
732     }
733     else
734         enum typeName = fullyQualifiedName!T;
735 
736     if (typeName[$-2..$] == "[]") return "slice";
737 
738     switch(typeName)
739     {
740         //Types that require special marshalling types
741         case voidTypeString: return "void";
742         case stringTypeString: return "slice";
743         case wstringTypeString: return "slice";
744         case dstringTypeString: return "slice";
745         case sysTimeTypeString: return "Marshalled_std_datetime_systime";
746         case dateTimeTypeString: return "Marshalled_std_datetime_date";
747         case timeOfDayTypeString: return "Marshalled_std_datetime_date";
748         case dateTypeString: return "Marshalled_std_datetime_date";
749         case durationTypeString: return "Marshalled_Duration";
750         case boolTypeString: return "bool";
751 
752         //Types that can be marshalled by default
753         case charTypeString: return "byte";
754         case wcharTypeString: return "char";
755         case dcharTypeString: return "uint";
756         case ubyteTypeString: return "byte";
757         case byteTypeString: return "sbyte";
758         case ushortTypeString: return "ushort";
759         case shortTypeString: return "short";
760         case uintTypeString: return "uint";
761         case intTypeString: return "int";
762         case ulongTypeString: return "ulong";
763         case longTypeString: return "long";
764         case floatTypeString: return "float";
765         case doubleTypeString: return "double";
766         default: return getCSharpName(typeName);
767     }
768 }
769 
770 private string getCSharpInterfaceType(string type) {
771     if (type[$-2..$] == "[]") type = type[0..$-2];
772 
773     switch (type) {
774         //Types that require special marshalling types
775         case voidTypeString: return "void";
776         case stringTypeString: return "string";
777         case wstringTypeString: return "string";
778         case dstringTypeString: return "string";
779         case sysTimeTypeString: return "DateTimeOffset";
780         case dateTimeTypeString: return "DateTime";
781         case timeOfDayTypeString: return "DateTime";
782         case dateTypeString: return "DateTime";
783         case durationTypeString: return "TimeSpan";
784         case boolTypeString: return "bool";
785 
786         //Types that can be marshalled by default
787         case charTypeString: return "byte";
788         case wcharTypeString: return "char";
789         case dcharTypeString: return "uint";
790         case ubyteTypeString: return "byte";
791         case byteTypeString: return "sbyte";
792         case ushortTypeString: return "ushort";
793         case shortTypeString: return "short";
794         case uintTypeString: return "uint";
795         case intTypeString: return "int";
796         case ulongTypeString: return "ulong";
797         case longTypeString: return "long";
798         case floatTypeString: return "float";
799         case doubleTypeString: return "double";
800         default: return getCSharpName(type);
801     }
802 }
803 
804 private string getDLangReturnType(T, Parent...)()
805     if(Parent.length <= 1)
806 {
807     import std.algorithm : among;
808     import std.traits : fullyQualifiedName;
809 
810     enum rtname = getReturnErrorTypeName(getDLangInterfaceType!T());
811 
812     static if(Parent.length == 1)
813     {
814         static if(is(T == Parent[0]))
815             enum typeName = __traits(identifier, T);
816         else
817             enum typeName = fullyQualifiedName!T;
818     }
819     else
820         enum typeName = fullyQualifiedName!T;
821 
822     //These types have predefined C# types.
823     if (typeName.among(dateTypeString, dateTimeTypeString, sysTimeTypeString, timeOfDayTypeString, durationTypeString,
824         voidTypeString, boolTypeString, stringTypeString, wstringTypeString, dstringTypeString) || rtname == "return_slice_error") {
825         return rtname;
826     }
827 
828     string typeStr = "    [GeneratedCodeAttribute(\"Autowrap\", \"1.0.0.0\")]
829     [StructLayout(LayoutKind.Sequential)]
830     internal struct " ~ rtname ~ " {
831         private void EnsureValid() {
832             var errStr = SharedFunctions.SliceToString(_error, DStringType._wstring);
833             if (!string.IsNullOrEmpty(errStr)) throw new DLangException(errStr);
834         }
835 ";
836 
837     static if (is(T == class) || is(T == interface))
838     {
839         typeStr ~= mixin(interp!"        public static implicit operator IntPtr(${rtname} ret) { ret.EnsureValid(); return ret._value; }${newline}");
840         typeStr ~= "        private IntPtr _value;" ~ newline;
841     }
842     else
843     {
844         typeStr ~= mixin(interp!"        public static implicit operator ${getCSharpInterfaceType(typeName)}(${rtname} ret) { ret.EnsureValid(); return ret._value; }${newline}");
845         typeStr ~= mixin(interp!"        private ${getCSharpInterfaceType(typeName)} _value;${newline}");
846     }
847     typeStr ~= "        private slice _error;" ~ newline;
848     typeStr ~= "    }" ~ newline ~ newline;
849 
850     if ((rtname in returnTypes) is null) {
851         returnTypes[rtname] = typeStr;
852     }
853     return rtname;
854 }
855 
856 private string getCSharpMethodInterfaceName(string aggName, string funcName) {
857     import autowrap.csharp.common : camelToPascalCase;
858     import std..string : split;
859     import std..string : replace;
860 
861     if (aggName == "DateTime" || aggName == "DateTimeOffset" || aggName == "TimeSpan")
862         aggName = "Datetime";
863 
864     string name;
865     if (aggName !is null && aggName != string.init)
866         name ~= camelToPascalCase(aggName) ~ "_";
867 
868     name ~= camelToPascalCase(funcName);
869 
870     return name.replace(".", "_");
871 }
872 
873 private string getReturnErrorTypeName(string aggName) {
874     import std..string : split;
875     import std.array : join;
876     return mixin(interp!"return_${aggName.split(\".\").join(\"_\")}_error");
877 }
878 
879 private string getCSharpName(string dlangName) {
880     import autowrap.csharp.common : camelToPascalCase;
881     import std.algorithm : map;
882     import std..string : split;
883     import std.array : join;
884     return dlangName.split(".").map!camelToPascalCase.join(".");
885 }
886 
887 private CSharpNamespace getNamespace(string name) {
888     return namespaces.require(name, new CSharpNamespace(name));
889 }
890 
891 private CSharpAggregate getAggregate(string namespace, string name, bool isStruct) {
892     CSharpNamespace ns = namespaces.require(namespace, new CSharpNamespace(namespace));
893     return ns.aggregates.require(name, new CSharpAggregate(name, isStruct));
894 }
895 
896 private void generateRangeDef(T)(string libraryName) {
897     import autowrap.csharp.common : getDLangSliceInterfaceName;
898     import std.traits : fullyQualifiedName;
899 
900     alias fqn = fullyQualifiedName!T;
901     const string csn = getCSharpName(fqn);
902 
903     rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "Create"));
904     rangeDef.functions ~= externFuncString.format("return_slice_error", getCSharpMethodInterfaceName(fqn, "Create"), "IntPtr capacity");
905     rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "Slice"));
906     rangeDef.functions ~= externFuncString.format("return_slice_error", getCSharpMethodInterfaceName(fqn, "Slice"), "slice dslice, IntPtr begin, IntPtr end");
907     rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "AppendSlice"));
908     rangeDef.functions ~= externFuncString.format("return_slice_error", getCSharpMethodInterfaceName(fqn, "AppendSlice"), "slice dslice, slice array");
909     rangeDef.constructors ~= mixin(interp!"            else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) this._slice = RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"Create\")}(new IntPtr(capacity));${newline}");
910     rangeDef.sliceEnd ~= mixin(interp!"            else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) return new Range<T>(RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"Slice\")}(_slice, new IntPtr(begin), _slice.length), DStringType.None);${newline}");
911     rangeDef.sliceRange ~= mixin(interp!"            else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) return new Range<T>(RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"Slice\")}(_slice, new IntPtr(begin), new IntPtr(end)), DStringType.None);${newline}");
912     rangeDef.setters ~= mixin(interp!"                else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"Set\")}(_slice, new IntPtr(i), (${getCSharpInterfaceType(fqn)})(object)value);${newline}");
913     rangeDef.appendValues ~= mixin(interp!"            else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) { this._slice = RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"AppendValue\")}(this._slice, (${getCSharpInterfaceType(fqn)})(object)value); }${newline}");
914     rangeDef.appendArrays ~= mixin(interp!"            else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) { range._slice = RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"AppendSlice\")}(range._slice, source._slice); return range; }${newline}");
915     if (is(T == class) || is(T == interface))
916     {
917         rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "Get"));
918         rangeDef.functions ~= externFuncString.format(getDLangReturnType!T(), getCSharpMethodInterfaceName(fqn, "Get"), "slice dslice, IntPtr index");
919         rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "Set"));
920         rangeDef.functions ~= externFuncString.format("return_void_error", getCSharpMethodInterfaceName(fqn, "Set"), "slice dslice, IntPtr index, IntPtr value");
921         rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "AppendValue"));
922         rangeDef.functions ~= externFuncString.format("return_slice_error", getCSharpMethodInterfaceName(fqn, "AppendValue"), "slice dslice, IntPtr value");
923         rangeDef.getters ~= mixin(interp!"                else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) return (T)(object)new ${getCSharpInterfaceType(fqn)}(RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"Get\")}(_slice, new IntPtr(i)));${newline}");
924         rangeDef.enumerators ~= mixin(interp!"                else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) yield return (T)(object)new ${getCSharpInterfaceType(fqn)}(RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"Get\")}(_slice, new IntPtr(i)));${newline}");
925     }
926     else
927     {
928         enum dlangIT = getDLangInterfaceType!T;
929         rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "Get"));
930         rangeDef.functions ~= externFuncString.format(getDLangReturnType!T(), getCSharpMethodInterfaceName(fqn, "Get"), "slice dslice, IntPtr index");
931         rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "Set"));
932         rangeDef.functions ~= externFuncString.format("return_void_error", getCSharpMethodInterfaceName(fqn, "Set"), mixin(interp!"slice dslice, IntPtr index, ${dlangIT} value"));
933         rangeDef.functions ~= dllImportString.format(libraryName, getDLangSliceInterfaceName(fqn, "AppendValue"));
934         rangeDef.functions ~= externFuncString.format("return_slice_error", getCSharpMethodInterfaceName(fqn, "AppendValue"), mixin(interp!"slice dslice, ${dlangIT} value"));
935         rangeDef.getters ~= mixin(interp!"                else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) return (T)(object)(${getCSharpInterfaceType(fqn)})RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"Get\")}(_slice, new IntPtr(i));${newline}");
936         rangeDef.enumerators ~= mixin(interp!"                else if (typeof(T) == typeof(${getCSharpInterfaceType(fqn)})) yield return (T)(object)(${getCSharpInterfaceType(fqn)})RangeFunctions.${getCSharpMethodInterfaceName(fqn, \"Get\")}(_slice, new IntPtr(i));${newline}");
937     }
938 }
939 
940 private void generateSliceBoilerplate(string libraryName) {
941     import std.datetime: DateTime, SysTime, Duration;
942 
943     void generateStringBoilerplate(string dlangType, string csharpType) {
944         rangeDef.enumerators ~= mixin(interp!"                else if (typeof(T) == typeof(string) && _strings == null && _type == DStringType._${dlangType}) yield return (T)(object)SharedFunctions.SliceToString(RangeFunctions.${csharpType}_Get(_slice, new IntPtr(i)), DStringType._${dlangType});${newline}");
945         rangeDef.getters ~= mixin(interp!"                else if (typeof(T) == typeof(string) && _strings == null && _type == DStringType._${dlangType}) return (T)(object)SharedFunctions.SliceToString(RangeFunctions.${csharpType}_Get(_slice, new IntPtr(i)), DStringType._${dlangType});${newline}");
946         rangeDef.setters ~= mixin(interp!"                else if (typeof(T) == typeof(string) && _strings == null && _type == DStringType._${dlangType}) RangeFunctions.${csharpType}_Set(_slice, new IntPtr(i), SharedFunctions.Create${csharpType}((string)(object)value));${newline}");
947         rangeDef.sliceEnd ~= mixin(interp!"            else if (typeof(T) == typeof(string) && _strings == null && _type == DStringType._${dlangType}) return new Range<T>(RangeFunctions.${csharpType}_Slice(_slice, new IntPtr(begin), _slice.length), _type);${newline}");
948         rangeDef.sliceRange ~= mixin(interp!"            else if (typeof(T) == typeof(string) && _strings == null && _type == DStringType._${dlangType}) return new Range<T>(RangeFunctions.${csharpType}_Slice(_slice, new IntPtr(begin), new IntPtr(end)), _type);${newline}");
949         rangeDef.appendValues ~= mixin(interp!"            else if (typeof(T) == typeof(string) && _strings == null && _type == DStringType._${dlangType}) { _slice = RangeFunctions.${csharpType}_AppendValue(_slice, SharedFunctions.Create${csharpType}((string)(object)value)); }${newline}");
950         rangeDef.appendArrays ~= mixin(interp!"            else if (typeof(T) == typeof(string) && range._strings == null && range._type == DStringType._${dlangType}) { range._slice = RangeFunctions.${csharpType}_AppendSlice(range._slice, source._slice); return range; }${newline}");
951 
952         rangeDef.functions ~= dllImportString.format(libraryName, mixin(interp!"autowrap_csharp_${csharpType}_Create"));
953         rangeDef.functions ~= externFuncString.format("return_slice_error", csharpType ~ "_Create", "IntPtr capacity");
954         rangeDef.functions ~= dllImportString.format(libraryName, mixin(interp!"autowrap_csharp_${csharpType}_Get"));
955         rangeDef.functions ~= externFuncString.format("return_slice_error", csharpType ~ "_Get", "slice dslice, IntPtr index");
956         rangeDef.functions ~= dllImportString.format(libraryName, mixin(interp!"autowrap_csharp_${csharpType}_Set"));
957         rangeDef.functions ~= externFuncString.format("return_void_error", csharpType ~ "_Set", "slice dslice, IntPtr index, slice value");
958         rangeDef.functions ~= dllImportString.format(libraryName, mixin(interp!"autowrap_csharp_${csharpType}_Slice"));
959         rangeDef.functions ~= externFuncString.format("return_slice_error", csharpType ~ "_Slice", "slice dslice, IntPtr begin, IntPtr end");
960         rangeDef.functions ~= dllImportString.format(libraryName, mixin(interp!"autowrap_csharp_${csharpType}_AppendValue"));
961         rangeDef.functions ~= externFuncString.format("return_slice_error", csharpType ~ "_AppendValue", "slice dslice, slice value");
962         rangeDef.functions ~= dllImportString.format(libraryName, mixin(interp!"autowrap_csharp_${csharpType}_AppendSlice"));
963         rangeDef.functions ~= externFuncString.format("return_slice_error", csharpType ~ "_AppendSlice", "slice dslice, slice array");
964     }
965 
966     //bool
967     rangeDef.functions ~= dllImportString.format(libraryName, "autowrap_csharp_Bool_Create");
968     rangeDef.functions ~= externFuncString.format("return_slice_error", "Bool_Create", "IntPtr capacity");
969     rangeDef.functions ~= dllImportString.format(libraryName, "autowrap_csharp_Bool_Get");
970     rangeDef.functions ~= "        [return: MarshalAs(UnmanagedType.Bool)]" ~ newline;
971     rangeDef.functions ~= externFuncString.format("return_bool_error", "Bool_Get", "slice dslice, IntPtr index");
972     rangeDef.functions ~= dllImportString.format(libraryName, "autowrap_csharp_Bool_Set");
973     rangeDef.functions ~= externFuncString.format("return_void_error", "Bool_Set", "slice dslice, IntPtr index, [MarshalAs(UnmanagedType.Bool)] bool value");
974     rangeDef.functions ~= dllImportString.format(libraryName, "autowrap_csharp_Bool_Slice");
975     rangeDef.functions ~= externFuncString.format("return_slice_error", "Bool_Slice", "slice dslice, IntPtr begin, IntPtr end");
976     rangeDef.functions ~= dllImportString.format(libraryName, "autowrap_csharp_Bool_AppendValue");
977     rangeDef.functions ~= externFuncString.format("return_slice_error", "Bool_AppendValue", "slice dslice, [MarshalAs(UnmanagedType.Bool)] bool value");
978     rangeDef.functions ~= dllImportString.format(libraryName, "autowrap_csharp_Bool_AppendSlice");
979     rangeDef.functions ~= externFuncString.format("return_slice_error", "Bool_AppendSlice", "slice dslice, slice array");
980     rangeDef.constructors ~= "            if (typeof(T) == typeof(bool)) this._slice = RangeFunctions.Bool_Create(new IntPtr(capacity));" ~ newline;
981     rangeDef.enumerators ~= "                if (typeof(T) == typeof(bool)) yield return (T)(object)RangeFunctions.Bool_Get(_slice, new IntPtr(i));" ~ newline;
982     rangeDef.getters ~= "                if (typeof(T) == typeof(bool)) return (T)(object)RangeFunctions.Bool_Get(_slice, new IntPtr(i));" ~ newline;
983     rangeDef.setters ~= "                if (typeof(T) == typeof(bool)) RangeFunctions.Bool_Set(_slice, new IntPtr(i), (bool)(object)value);" ~ newline;
984     rangeDef.sliceEnd ~= "            if (typeof(T) == typeof(bool)) return new Range<T>(RangeFunctions.Bool_Slice(_slice, new IntPtr(begin), _slice.length), DStringType.None);" ~ newline;
985     rangeDef.sliceRange ~= "            if (typeof(T) == typeof(bool)) return new Range<T>(RangeFunctions.Bool_Slice(_slice, new IntPtr(begin), new IntPtr(end)), DStringType.None);" ~ newline;
986     rangeDef.appendValues ~= "            if (typeof(T) == typeof(bool)) { this._slice = RangeFunctions.Bool_AppendValue(this._slice, (bool)(object)value); }" ~ newline;
987     rangeDef.appendArrays ~= "            if (typeof(T) == typeof(bool)) { range._slice = RangeFunctions.Bool_AppendSlice(range._slice, source._slice); return range; }" ~ newline;
988 
989     rangeDef.enumerators ~= "                else if (typeof(T) == typeof(string) && _strings != null && _type == DStringType.None) yield return (T)(object)_strings[(int)i];" ~ newline;
990     rangeDef.getters ~= "                else if (typeof(T) == typeof(string) && _strings != null && _type == DStringType.None) return (T)(object)_strings[(int)i];" ~ newline;
991     rangeDef.setters ~= "                else if (typeof(T) == typeof(string) && _strings != null && _type == DStringType.None) _strings[(int)i] = (string)(object)value;" ~ newline;
992     rangeDef.sliceEnd ~= "            else if (typeof(T) == typeof(string) && _strings != null && _type == DStringType.None) return new Range<T>((IEnumerable<T>)_strings.Skip((int)begin));" ~ newline;
993     rangeDef.sliceRange ~= "            else if (typeof(T) == typeof(string) && _strings != null && _type == DStringType.None) return new Range<T>((IEnumerable<T>)_strings.Skip((int)begin).Take((int)end - (int)begin));" ~ newline;
994     rangeDef.appendValues ~= "            else if (typeof(T) == typeof(string) && this._strings != null && this._type == DStringType.None) { this._strings.Add((string)(object)value); }" ~ newline;
995     rangeDef.appendArrays ~= "            else if (typeof(T) == typeof(string) && range._strings != null && range._type == DStringType.None) { foreach(T s in source) range._strings.Add((string)(object)s); return range; }" ~ newline;
996 
997     generateStringBoilerplate("string", "String");
998     generateStringBoilerplate("wstring", "Wstring");
999     generateStringBoilerplate("dstring", "Dstring");
1000 
1001     generateRangeDef!byte(libraryName);
1002     generateRangeDef!ubyte(libraryName);
1003     generateRangeDef!short(libraryName);
1004     generateRangeDef!ushort(libraryName);
1005     generateRangeDef!int(libraryName);
1006     generateRangeDef!uint(libraryName);
1007     generateRangeDef!long(libraryName);
1008     generateRangeDef!ulong(libraryName);
1009     generateRangeDef!float(libraryName);
1010     generateRangeDef!double(libraryName);
1011 
1012     generateRangeDef!DateTime(libraryName);
1013     generateRangeDef!SysTime(libraryName);
1014     generateRangeDef!Duration(libraryName);
1015 }
1016 
1017 private string writeCSharpBoilerplate(string libraryName, string rootNamespace) {
1018     string returnTypesStr = string.init;
1019     foreach(string rt; returnTypes.byValue) {
1020         returnTypesStr ~= rt;
1021     }
1022     immutable string boilerplate = import("Boilerplate.cs");
1023     return boilerplate.format(libraryName, rootNamespace, returnTypesStr, rangeDef.toString());
1024 }
1025 
1026 // C# doesn't allow members to have the same name as the class or struct,
1027 // because it would conflict with how they name constructors.
1028 string getCSMemberName(string dlangTypeName, string dlangMemberName)
1029 {
1030     import autowrap.csharp.common : camelToPascalCase;
1031     string retval = camelToPascalCase(dlangMemberName);
1032     if(retval == getCSharpName(dlangTypeName))
1033         retval ~= "_";
1034     return retval;
1035 }
1036 
1037 Tuple!(string, "public_", string, "private_") getCSFieldNameTuple(string dlangTypeName, string dlangFieldName)
1038 {
1039     import std.typecons : tuple;
1040     auto memberName = getCSMemberName(dlangTypeName, dlangFieldName);
1041     return typeof(return)(memberName, "_" ~ memberName);
1042 }
1043 
1044 private template GetterPropertyType(T, string memberName)
1045 {
1046     import std.meta : AliasSeq, staticMap;
1047     import std.traits : fullyQualifiedName;
1048 
1049     alias GetterPropertyType = staticMap!(GetterType, __traits(getOverloads, T, memberName));
1050     static assert(GetterPropertyType.length <= 1,
1051                   "It should not be possible to have multiple getters with the same name. " ~
1052                   fullyQualifiedName!T ~ "." ~ memberName);
1053 
1054     static template GetterType(alias func)
1055     {
1056         import std.traits : FunctionAttribute, functionAttributes, Parameters, ReturnType;
1057 
1058         static if((functionAttributes!func & FunctionAttribute.property) != 0 &&
1059                   !is(ReturnType!func == void) &&
1060                   Parameters!func.length == 0)
1061         {
1062             alias GetterType = ReturnType!func;
1063         }
1064         else
1065             alias GetterType = AliasSeq!();
1066     }
1067 }
1068 
1069 // This does ignore getters which return by ref, but they can be checked by
1070 // looking at the getter, and it wouldn't work to use such a function as a
1071 // setter without an extra D function around it to call it, which we don't
1072 // currently do. But even if we wanted to handle such a case, it's simpler to
1073 // check the getter than to treat it as another setter.
1074 private template SetterPropertyTypes(T, string memberName)
1075 {
1076     import std.meta : AliasSeq, staticMap;
1077 
1078     alias SetterPropertyTypes = staticMap!(SetterType, __traits(getOverloads, T, memberName));
1079 
1080     static template SetterType(alias func)
1081     {
1082         import std.traits : FunctionAttribute, functionAttributes, Parameters, ReturnType;
1083 
1084         alias ParamTypes = Parameters!func;
1085 
1086         static if(ParamTypes.length == 1 && (functionAttributes!func & FunctionAttribute.property) != 0)
1087             alias SetterType = ParamTypes;
1088         else
1089             alias SetterType = AliasSeq!();
1090     }
1091 }
1092 
1093 // Unfortunately, while these tests have been tested on their own, they don't
1094 // currently run as part of autowrap's tests, because dub test doesn't work for
1095 // the csharp folder, and the top level one does not run them.
1096 unittest
1097 {
1098     import std.meta : AliasSeq;
1099 
1100     static struct S
1101     {
1102         @property void noprop() { assert(0); }
1103         @property void noprop(int, int) { assert(0); }
1104         @property int noprop(int, int) { assert(0); }
1105 
1106         int noprop2() { assert(0); }
1107         void noprop2(string) { assert(0); }
1108         int noprop2(int) { assert(0); }
1109         bool noprop2(float) { assert(0); }
1110 
1111         @property int getter() { assert(0); }
1112         @property void getter(int, int) { assert(0); }
1113         @property string getter(string, int) { assert(0); }
1114 
1115         @property ref string getter2() { assert(0); }
1116         void getter2(string) { assert(0); }
1117         ref int getter2(int) { assert(0); }
1118         bool getter2(float) { assert(0); }
1119 
1120         @property string setter(int) { assert(0); }
1121         @property string setter(float, bool) { assert(0); }
1122         @property int setter(bool, int) { assert(0); }
1123 
1124         int setter2() { assert(0); }
1125         @property void setter2(string) { assert(0); }
1126         @property int setter2(int) { assert(0); }
1127         @property bool setter2(wstring, bool) { assert(0); }
1128         @property bool setter2(float) { assert(0); }
1129 
1130         @property int both() { assert(0); }
1131         @property void both(string) { assert(0); }
1132         @property int both(int) { assert(0); }
1133         @property bool both(float) { assert(0); }
1134     }
1135 
1136     static assert(GetterPropertyType!(S, "noprop").length == 0);
1137     static assert(SetterPropertyTypes!(S, "noprop").length == 0);
1138 
1139     static assert(GetterPropertyType!(S, "noprop2").length == 0);
1140     static assert(SetterPropertyTypes!(S, "noprop2").length == 0);
1141 
1142     static assert(is(GetterPropertyType!(S, "getter") == AliasSeq!int));
1143     static assert(SetterPropertyTypes!(S, "getter").length == 0);
1144 
1145     static assert(is(GetterPropertyType!(S, "getter2") == AliasSeq!string));
1146     static assert(SetterPropertyTypes!(S, "getter2").length == 0);
1147 
1148     static assert(GetterPropertyType!(S, "setter").length == 0);
1149     static assert(is(SetterPropertyTypes!(S, "setter") == AliasSeq!int));
1150 
1151     static assert(GetterPropertyType!(S, "setter2").length == 0);
1152     static assert(is(SetterPropertyTypes!(S, "setter2") == AliasSeq!(string, int, float)));
1153 
1154     static assert(is(GetterPropertyType!(S, "both") == AliasSeq!int));
1155     static assert(is(SetterPropertyTypes!(S, "both") == AliasSeq!(string, int, float)));
1156 }
1157 
1158 private template isNotVoid(T...)
1159     if(T.length == 1)
1160 {
1161     enum isNotVoid = !is(T[0] == void);
1162 }