Use new dsymbol code

This commit is contained in:
Hackerpilot 2015-08-03 16:38:40 -07:00
parent eb79c2a7ab
commit b92b8944b5
2 changed files with 79 additions and 66 deletions

View File

@ -20,6 +20,7 @@ module server.autocomplete;
import std.algorithm; import std.algorithm;
import std.experimental.allocator; import std.experimental.allocator;
import std.experimental.logger;
import std.array; import std.array;
import std.conv; import std.conv;
import std.experimental.logger; import std.experimental.logger;
@ -48,8 +49,6 @@ import memory.allocators;
import common.constants; import common.constants;
import common.messages; import common.messages;
private alias ASTAllocator = CAllocatorImpl!(AllocatorList!(n => Region!Mallocator(1024 * 64)));
/** /**
* Gets documentation for the symbol at the cursor * Gets documentation for the symbol at the cursor
* Params: * Params:
@ -57,14 +56,15 @@ private alias ASTAllocator = CAllocatorImpl!(AllocatorList!(n => Region!Mallocat
* Returns: * Returns:
* the autocompletion response * the autocompletion response
*/ */
public AutocompleteResponse getDoc(const AutocompleteRequest request) public AutocompleteResponse getDoc(const AutocompleteRequest request,
ref ModuleCache moduleCache)
{ {
// trace("Getting doc comments"); // trace("Getting doc comments");
AutocompleteResponse response; AutocompleteResponse response;
auto allocator = scoped!(ASTAllocator)(); auto allocator = scoped!(ASTAllocator)();
auto cache = StringCache(StringCache.defaultBucketCount); auto cache = StringCache(StringCache.defaultBucketCount);
SymbolStuff stuff = getSymbolsForCompletion(request, CompletionType.ddoc, SymbolStuff stuff = getSymbolsForCompletion(request, CompletionType.ddoc,
allocator, &cache); allocator, cache, moduleCache);
if (stuff.symbols.length == 0) if (stuff.symbols.length == 0)
warning("Could not find symbol"); warning("Could not find symbol");
else foreach (symbol; stuff.symbols.filter!(a => !a.doc.empty)) else foreach (symbol; stuff.symbols.filter!(a => !a.doc.empty))
@ -79,13 +79,14 @@ public AutocompleteResponse getDoc(const AutocompleteRequest request)
* Returns: * Returns:
* the autocompletion response * the autocompletion response
*/ */
public AutocompleteResponse findDeclaration(const AutocompleteRequest request) public AutocompleteResponse findDeclaration(const AutocompleteRequest request,
ref ModuleCache moduleCache)
{ {
AutocompleteResponse response; AutocompleteResponse response;
auto allocator = scoped!(ASTAllocator)(); auto allocator = scoped!(ASTAllocator)();
auto cache = StringCache(StringCache.defaultBucketCount); auto cache = StringCache(StringCache.defaultBucketCount);
SymbolStuff stuff = getSymbolsForCompletion(request, SymbolStuff stuff = getSymbolsForCompletion(request,
CompletionType.location, allocator, &cache); CompletionType.location, allocator, cache, moduleCache);
scope(exit) stuff.destroy(); scope(exit) stuff.destroy();
if (stuff.symbols.length > 0) if (stuff.symbols.length > 0)
{ {
@ -104,40 +105,45 @@ public AutocompleteResponse findDeclaration(const AutocompleteRequest request)
* Returns: * Returns:
* the autocompletion response * the autocompletion response
*/ */
public AutocompleteResponse complete(const AutocompleteRequest request) public AutocompleteResponse complete(const AutocompleteRequest request,
ref ModuleCache moduleCache)
{ {
const(Token)[] tokenArray; const(Token)[] tokenArray;
auto cache = StringCache(StringCache.defaultBucketCount); auto stringCache = StringCache(StringCache.defaultBucketCount);
auto beforeTokens = getTokensBeforeCursor(request.sourceCode, auto beforeTokens = getTokensBeforeCursor(request.sourceCode,
request.cursorPosition, &cache, tokenArray); request.cursorPosition, stringCache, tokenArray);
if (beforeTokens.length >= 2) if (beforeTokens.length >= 2)
{ {
if (beforeTokens[$ - 1] == tok!"(" || beforeTokens[$ - 1] == tok!"[") if (beforeTokens[$ - 1] == tok!"(" || beforeTokens[$ - 1] == tok!"[")
{ {
return parenCompletion(beforeTokens, tokenArray, request.cursorPosition); return parenCompletion(beforeTokens, tokenArray, request.cursorPosition,
moduleCache);
} }
else if (beforeTokens[$ - 1] == tok!",") else if (beforeTokens[$ - 1] == tok!",")
{ {
immutable size_t end = goBackToOpenParen(beforeTokens); immutable size_t end = goBackToOpenParen(beforeTokens);
if (end != size_t.max) if (end != size_t.max)
return parenCompletion(beforeTokens[0 .. end], tokenArray, request.cursorPosition); return parenCompletion(beforeTokens[0 .. end], tokenArray,
request.cursorPosition, moduleCache);
} }
else else
{ {
ImportKind kind = determineImportKind(beforeTokens); ImportKind kind = determineImportKind(beforeTokens);
if (kind == ImportKind.neither) if (kind == ImportKind.neither)
return dotCompletion(beforeTokens, tokenArray, request.cursorPosition); return dotCompletion(beforeTokens, tokenArray, request.cursorPosition,
moduleCache);
else else
return importCompletion(beforeTokens, kind); return importCompletion(beforeTokens, kind, moduleCache);
} }
} }
return dotCompletion(beforeTokens, tokenArray, request.cursorPosition); return dotCompletion(beforeTokens, tokenArray, request.cursorPosition, moduleCache);
} }
/** /**
* *
*/ */
public AutocompleteResponse symbolSearch(const AutocompleteRequest request) public AutocompleteResponse symbolSearch(const AutocompleteRequest request,
ref ModuleCache moduleCache)
{ {
import containers.ttree : TTree; import containers.ttree : TTree;
@ -148,7 +154,7 @@ public AutocompleteResponse symbolSearch(const AutocompleteRequest request)
config, &cache); config, &cache);
auto allocator = scoped!(ASTAllocator)(); auto allocator = scoped!(ASTAllocator)();
ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
request.cursorPosition); request.cursorPosition, moduleCache);
scope(exit) pair.destroy(); scope(exit) pair.destroy();
static struct SearchResults static struct SearchResults
@ -183,7 +189,7 @@ public AutocompleteResponse symbolSearch(const AutocompleteRequest request)
{ {
symbol.getAllPartsNamed(request.searchName, results); symbol.getAllPartsNamed(request.searchName, results);
} }
foreach (s; ModuleCache.getAllSymbols()) foreach (s; moduleCache.getAllSymbols())
{ {
s.symbol.getAllPartsNamed(request.searchName, results); s.symbol.getAllPartsNamed(request.searchName, results);
} }
@ -203,7 +209,7 @@ public AutocompleteResponse symbolSearch(const AutocompleteRequest request)
/******************************************************************************/ /******************************************************************************/
private: private:
enum ImportKind enum ImportKind : ubyte
{ {
selective, selective,
normal, normal,
@ -219,8 +225,8 @@ enum ImportKind
* Returns: * Returns:
* the autocompletion response * the autocompletion response
*/ */
AutocompleteResponse dotCompletion(T)(T beforeTokens, AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray,
const(Token)[] tokenArray, size_t cursorPosition) size_t cursorPosition, ref ModuleCache moduleCache)
{ {
AutocompleteResponse response; AutocompleteResponse response;
@ -239,8 +245,8 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens,
// responses when the cursor is in the middle of an identifier instead // responses when the cursor is in the middle of an identifier instead
// of at the end // of at the end
auto t = beforeTokens[$ - 1]; auto t = beforeTokens[$ - 1];
partial = t.text[0 .. cursorPosition - t.index]; if (cursorPosition - t.index >= 0 && cursorPosition - t.index <= t.text.length)
partial = t.text[0 .. cursorPosition - t.index];
significantTokenType = tok!"identifier"; significantTokenType = tok!"identifier";
beforeTokens = beforeTokens[0 .. $ - 1]; beforeTokens = beforeTokens[0 .. $ - 1];
} }
@ -291,7 +297,7 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens,
case tok!"super": case tok!"super":
auto allocator = scoped!(ASTAllocator)(); auto allocator = scoped!(ASTAllocator)();
ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
cursorPosition); cursorPosition, moduleCache);
scope(exit) pair.destroy(); scope(exit) pair.destroy();
response.setCompletions(pair.scope_, getExpression(beforeTokens), response.setCompletions(pair.scope_, getExpression(beforeTokens),
cursorPosition, CompletionType.identifiers, false, partial); cursorPosition, CompletionType.identifiers, false, partial);
@ -316,11 +322,11 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens,
* a sorted range of tokens before the cursor position * a sorted range of tokens before the cursor position
*/ */
auto getTokensBeforeCursor(const(ubyte[]) sourceCode, size_t cursorPosition, auto getTokensBeforeCursor(const(ubyte[]) sourceCode, size_t cursorPosition,
StringCache* cache, out const(Token)[] tokenArray) ref StringCache cache, out const(Token)[] tokenArray)
{ {
LexerConfig config; LexerConfig config;
config.fileName = ""; config.fileName = "";
tokenArray = getTokensForParser(cast(ubyte[]) sourceCode, config, cache); tokenArray = getTokensForParser(cast(ubyte[]) sourceCode, config, &cache);
auto sortedTokens = assumeSorted(tokenArray); auto sortedTokens = assumeSorted(tokenArray);
return sortedTokens.lowerBound(cast(size_t) cursorPosition); return sortedTokens.lowerBound(cast(size_t) cursorPosition);
} }
@ -334,13 +340,14 @@ auto getTokensBeforeCursor(const(ubyte[]) sourceCode, size_t cursorPosition,
* the request's source code, cursor position, and completion type. * the request's source code, cursor position, and completion type.
*/ */
SymbolStuff getSymbolsForCompletion(const AutocompleteRequest request, SymbolStuff getSymbolsForCompletion(const AutocompleteRequest request,
const CompletionType type, IAllocator allocator, StringCache* cache) const CompletionType type, IAllocator allocator, ref StringCache cache,
ref ModuleCache moduleCache)
{ {
const(Token)[] tokenArray; const(Token)[] tokenArray;
auto beforeTokens = getTokensBeforeCursor(request.sourceCode, auto beforeTokens = getTokensBeforeCursor(request.sourceCode,
request.cursorPosition, cache, tokenArray); request.cursorPosition, cache, tokenArray);
ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
request.cursorPosition); request.cursorPosition, moduleCache);
auto expression = getExpression(beforeTokens); auto expression = getExpression(beforeTokens);
return SymbolStuff(getSymbolsByTokenChain(pair.scope_, expression, return SymbolStuff(getSymbolsByTokenChain(pair.scope_, expression,
request.cursorPosition, type), pair.symbol, pair.scope_); request.cursorPosition, type), pair.symbol, pair.scope_);
@ -369,7 +376,7 @@ struct SymbolStuff
* the autocompletion response * the autocompletion response
*/ */
AutocompleteResponse parenCompletion(T)(T beforeTokens, AutocompleteResponse parenCompletion(T)(T beforeTokens,
const(Token)[] tokenArray, size_t cursorPosition) const(Token)[] tokenArray, size_t cursorPosition, ref ModuleCache moduleCache)
{ {
AutocompleteResponse response; AutocompleteResponse response;
immutable(string)[] completions; immutable(string)[] completions;
@ -418,7 +425,7 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens,
case tok!"]": case tok!"]":
auto allocator = scoped!(ASTAllocator)(); auto allocator = scoped!(ASTAllocator)();
ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
cursorPosition); cursorPosition, moduleCache);
scope(exit) pair.destroy(); scope(exit) pair.destroy();
auto expression = getExpression(beforeTokens[0 .. $ - 1]); auto expression = getExpression(beforeTokens[0 .. $ - 1]);
response.setCompletions(pair.scope_, expression, response.setCompletions(pair.scope_, expression,
@ -437,7 +444,8 @@ ImportKind determineImportKind(T)(T tokens)
{ {
assert (tokens.length > 1); assert (tokens.length > 1);
size_t i = tokens.length - 1; size_t i = tokens.length - 1;
if (!(tokens[i] == tok!":" || tokens[i] == tok!"," || tokens[i] == tok!"." || tokens[i] == tok!"identifier")) if (!(tokens[i] == tok!":" || tokens[i] == tok!"," || tokens[i] == tok!"."
|| tokens[i] == tok!"identifier"))
return ImportKind.neither; return ImportKind.neither;
bool foundColon = false; bool foundColon = false;
while (true) switch (tokens[i].type) while (true) switch (tokens[i].type)
@ -484,7 +492,8 @@ unittest
* import std.algorithm: balancedParens; * import std.algorithm: balancedParens;
* --- * ---
*/ */
AutocompleteResponse importCompletion(T)(T beforeTokens, ImportKind kind) AutocompleteResponse importCompletion(T)(T beforeTokens, ImportKind kind,
ref ModuleCache moduleCache)
in in
{ {
assert (beforeTokens.length >= 2); assert (beforeTokens.length >= 2);
@ -501,7 +510,7 @@ body
{ {
while (beforeTokens[i].type != tok!"," && beforeTokens[i].type != tok!"import") i--; while (beforeTokens[i].type != tok!"," && beforeTokens[i].type != tok!"import") i--;
setImportCompletions(beforeTokens[i .. $], response); setImportCompletions(beforeTokens[i .. $], response, moduleCache);
return response; return response;
} }
@ -550,13 +559,13 @@ body
} }
} }
string resolvedLocation = ModuleCache.resolveImportLocation(path); string resolvedLocation = moduleCache.resolveImportLocation(path);
if (resolvedLocation is null) if (resolvedLocation is null)
{ {
warning("Could not resolve location of ", path); warning("Could not resolve location of ", path);
return response; return response;
} }
auto symbols = ModuleCache.getModuleSymbol(resolvedLocation); auto symbols = moduleCache.getModuleSymbol(internString(resolvedLocation));
import containers.hashset : HashSet; import containers.hashset : HashSet;
HashSet!string h; HashSet!string h;
@ -575,8 +584,9 @@ body
foreach (s; symbols.opSlice()) foreach (s; symbols.opSlice())
{ {
if (s.kind == CompletionKind.importSymbol) foreach (sy; s.type.opSlice()) if (s.kind == CompletionKind.importSymbol && s.type !is null)
addSymbolToResponses(sy); foreach (sy; s.type.opSlice())
addSymbolToResponses(sy);
else else
addSymbolToResponses(s); addSymbolToResponses(s);
} }
@ -590,7 +600,8 @@ body
* tokens = the tokens after the "import" keyword and before the cursor * tokens = the tokens after the "import" keyword and before the cursor
* response = the response that should be populated * response = the response that should be populated
*/ */
void setImportCompletions(T)(T tokens, ref AutocompleteResponse response) void setImportCompletions(T)(T tokens, ref AutocompleteResponse response,
ref ModuleCache cache)
{ {
response.completionType = CompletionType.identifiers; response.completionType = CompletionType.identifiers;
string partial = null; string partial = null;
@ -604,7 +615,7 @@ void setImportCompletions(T)(T tokens, ref AutocompleteResponse response)
bool found = false; bool found = false;
foreach (importDirectory; ModuleCache.getImportPaths()) foreach (importDirectory; cache.getImportPaths())
{ {
string p = buildPath(importDirectory, path); string p = buildPath(importDirectory, path);
if (!exists(p)) if (!exists(p))
@ -732,11 +743,11 @@ DSymbol*[] getSymbolsByTokenChain(T)(Scope* completionScope,
break loop; break loop;
} }
// Log.trace("looking for ", tokens[i].text, " in ", symbols[0].name); // trace("looking for ", tokens[i].text, " in ", symbols[0].name);
symbols = symbols[0].getPartsByName(internString(tokens[i].text)); symbols = symbols[0].getPartsByName(internString(tokens[i].text));
if (symbols.length == 0) if (symbols.length == 0)
{ {
// Log.trace("Couldn't find it."); // trace("Couldn't find it.");
break loop; break loop;
} }
if (shouldSwapWithType(completionType, symbols[0].kind, i, if (shouldSwapWithType(completionType, symbols[0].kind, i,
@ -820,7 +831,8 @@ void setCompletions(T)(ref AutocompleteResponse response,
// based on the currently entered text. // based on the currently entered text.
if (partial !is null && tokens.length == 0) if (partial !is null && tokens.length == 0)
{ {
foreach (s; completionScope.getSymbolsInCursorScope(cursorPosition) auto currentSymbols = completionScope.getSymbolsInCursorScope(cursorPosition);
foreach (s; currentSymbols
.filter!(a => a.name.toUpper().startsWith(partial.toUpper()))) .filter!(a => a.name.toUpper().startsWith(partial.toUpper())))
{ {
response.completionKinds ~= s.kind; response.completionKinds ~= s.kind;
@ -848,26 +860,24 @@ void setCompletions(T)(ref AutocompleteResponse response,
if (symbols.length == 0) if (symbols.length == 0)
return; return;
} }
foreach (sym; symbols[0].opSlice()) if (symbols[0].kind != CompletionKind.importSymbol)
{ {
if (sym.kind == CompletionKind.importSymbol) foreach (s; sym.type.opSlice()) foreach (sym; symbols[0].opSlice())
{ {
response.completionKinds ~= s.kind; if (sym.name !is null && sym.name.length > 0 && sym.name[0] != '*'
response.completions ~= s.name.dup; && (partial is null ? true : sym.name.toUpper().startsWith(partial.toUpper()))
} && !response.completions.canFind(sym.name))
else if (sym.name !is null && sym.name.length > 0 && sym.name[0] != '*' {
&& (partial is null ? true : sym.name.toUpper().startsWith(partial.toUpper())) response.completionKinds ~= sym.kind;
&& !response.completions.canFind(sym.name)) response.completions ~= sym.name.dup;
{ }
response.completionKinds ~= sym.kind;
response.completions ~= sym.name.dup;
} }
} }
response.completionType = CompletionType.identifiers; response.completionType = CompletionType.identifiers;
} }
else if (completionType == CompletionType.calltips) else if (completionType == CompletionType.calltips)
{ {
// Log.trace("Showing call tips for ", symbols[0].name, " of kind ", symbols[0].kind); // trace("Showing call tips for ", symbols[0].name, " of kind ", symbols[0].kind);
if (symbols[0].kind != CompletionKind.functionName if (symbols[0].kind != CompletionKind.functionName
&& symbols[0].callTip is null) && symbols[0].callTip is null)
{ {

View File

@ -58,12 +58,13 @@ int main(string[] args)
ushort port = 9166; ushort port = 9166;
bool help; bool help;
bool printVersion; bool printVersion;
bool ignoreConfig;
string[] importPaths; string[] importPaths;
try try
{ {
getopt(args, "port|p", &port, "I", &importPaths, "help|h", &help, getopt(args, "port|p", &port, "I", &importPaths, "help|h", &help,
"version", & printVersion); "version", &printVersion, "ignoreConfig", &ignoreConfig);
} }
catch (ConvException e) catch (ConvException e)
{ {
@ -87,7 +88,8 @@ int main(string[] args)
return 0; return 0;
} }
importPaths ~= loadConfiguredImportDirs(); if (!ignoreConfig)
importPaths ~= loadConfiguredImportDirs();
auto socket = new TcpSocket(AddressFamily.INET); auto socket = new TcpSocket(AddressFamily.INET);
socket.blocking = true; socket.blocking = true;
@ -102,14 +104,15 @@ int main(string[] args)
info("Sockets shut down."); info("Sockets shut down.");
} }
ModuleCache.addImportPaths(importPaths); ModuleCache cache = ModuleCache(new ASTAllocator);
infof("Import directories:\n %-(%s\n %)", ModuleCache.getImportPaths()); cache.addImportPaths(importPaths);
infof("Import directories:\n %-(%s\n %)", cache.getImportPaths());
ubyte[] buffer = cast(ubyte[]) Mallocator.it.allocate(1024 * 1024 * 4); // 4 megabytes should be enough for anybody... ubyte[] buffer = cast(ubyte[]) Mallocator.it.allocate(1024 * 1024 * 4); // 4 megabytes should be enough for anybody...
scope(exit) Mallocator.it.deallocate(buffer); scope(exit) Mallocator.it.deallocate(buffer);
sw.stop(); sw.stop();
info(ModuleCache.symbolsAllocated, " symbols cached."); info(cache.symbolsAllocated, " symbols cached.");
info("Startup completed in ", sw.peek().to!("msecs", float), " milliseconds."); info("Startup completed in ", sw.peek().to!("msecs", float), " milliseconds.");
// No relative paths // No relative paths
@ -167,7 +170,7 @@ int main(string[] args)
if (request.kind & RequestKind.clearCache) if (request.kind & RequestKind.clearCache)
{ {
info("Clearing cache."); info("Clearing cache.");
ModuleCache.clear(); cache.clear();
} }
else if (request.kind & RequestKind.shutdown) else if (request.kind & RequestKind.shutdown)
{ {
@ -184,12 +187,12 @@ int main(string[] args)
} }
if (request.kind & RequestKind.addImport) if (request.kind & RequestKind.addImport)
{ {
ModuleCache.addImportPaths(request.importPaths); cache.addImportPaths(request.importPaths);
} }
if (request.kind & RequestKind.listImports) if (request.kind & RequestKind.listImports)
{ {
AutocompleteResponse response; AutocompleteResponse response;
response.importPaths = ModuleCache.getImportPaths().array(); response.importPaths = cache.getImportPaths().array();
ubyte[] responseBytes = msgpack.pack(response); ubyte[] responseBytes = msgpack.pack(response);
info("Returning import path list"); info("Returning import path list");
s.send(responseBytes); s.send(responseBytes);
@ -197,7 +200,7 @@ int main(string[] args)
else if (request.kind & RequestKind.autocomplete) else if (request.kind & RequestKind.autocomplete)
{ {
info("Getting completions"); info("Getting completions");
AutocompleteResponse response = complete(request); AutocompleteResponse response = complete(request, cache);
ubyte[] responseBytes = msgpack.pack(response); ubyte[] responseBytes = msgpack.pack(response);
s.send(responseBytes); s.send(responseBytes);
} }
@ -206,7 +209,7 @@ int main(string[] args)
info("Getting doc comment"); info("Getting doc comment");
try try
{ {
AutocompleteResponse response = getDoc(request); AutocompleteResponse response = getDoc(request, cache);
ubyte[] responseBytes = msgpack.pack(response); ubyte[] responseBytes = msgpack.pack(response);
s.send(responseBytes); s.send(responseBytes);
} }
@ -219,7 +222,7 @@ int main(string[] args)
{ {
try try
{ {
AutocompleteResponse response = findDeclaration(request); AutocompleteResponse response = findDeclaration(request, cache);
ubyte[] responseBytes = msgpack.pack(response); ubyte[] responseBytes = msgpack.pack(response);
s.send(responseBytes); s.send(responseBytes);
} }
@ -230,7 +233,7 @@ int main(string[] args)
} }
else if (request.kind & RequestKind.search) else if (request.kind & RequestKind.search)
{ {
AutocompleteResponse response = symbolSearch(request); AutocompleteResponse response = symbolSearch(request, cache);
ubyte[] responseBytes = msgpack.pack(response); ubyte[] responseBytes = msgpack.pack(response);
s.send(responseBytes); s.send(responseBytes);
} }
@ -304,7 +307,7 @@ string[] loadConfiguredImportDirs()
info("Loading configuration from ", configLocation); info("Loading configuration from ", configLocation);
File f = File(configLocation, "rt"); File f = File(configLocation, "rt");
return f.byLine(KeepTerminator.no) return f.byLine(KeepTerminator.no)
.filter!(a => a.length > 0 && existanceCheck(a)) .filter!(a => a.length > 0 && a[0] != '#' && existanceCheck(a))
.map!(a => a.idup) .map!(a => a.idup)
.array(); .array();
} }