1 /// Finds methods in a specified interface or class location. 2 module workspaced.visitors.methodfinder; 3 4 import workspaced.visitors.attributes; 5 6 import workspaced.dparseext; 7 8 import dparse.ast; 9 import dparse.formatter; 10 import dparse.lexer; 11 12 import std.algorithm; 13 import std.array; 14 import std.range; 15 import std.string; 16 17 /// Information about an argument in a method defintion. 18 struct ArgumentInfo 19 { 20 /// The whole definition of the argument including everything related to it as formatted code string. 21 string signature; 22 /// The type of the argument. 23 string type; 24 /// The name of the argument. 25 string name; 26 27 /// Returns just the name. 28 string toString() const 29 { 30 return name; 31 } 32 } 33 34 /// Information about a method definition. 35 struct MethodDetails 36 { 37 /// The name of the method. 38 string name; 39 /// The type definition of the method without body, abstract or final. 40 string signature; 41 /// The return type of the method. 42 string returnType; 43 /// All (regular) arguments passable into this method. 44 ArgumentInfo[] arguments; 45 /// 46 bool isNothrowOrNogc; 47 /// True if this function has an implementation. 48 bool hasBody; 49 /// True when the container is an interface or (optionally implicit) abstract class or when in class not having a body. 50 bool needsImplementation; 51 /// True when in a class and method doesn't have a body. 52 bool optionalImplementation; 53 /// Range starting at return type, going until last token before opening curly brace. 54 size_t[2] definitionRange; 55 /// Range containing the starting and ending braces of the body. 56 size_t[2] blockRange; 57 58 /// Signature without any attributes, constraints or parameter details other than types. 59 /// Used to differentiate a method from others without computing the mangle. 60 /// Returns: `"<type> <name>(<argument types>)"` 61 string identifier() 62 { 63 return format("%s %s(%(%s,%))", returnType, name, arguments.map!"a.type"); 64 } 65 } 66 67 /// 68 struct FieldDetails 69 { 70 /// 71 string name, type; 72 /// 73 bool isPrivate; 74 } 75 76 /// 77 struct TypeDetails 78 { 79 enum Type 80 { 81 none, 82 class_, 83 interface_, 84 enum_, 85 struct_, 86 union_, 87 alias_, 88 template_, 89 } 90 91 /// Name in last element, all parents in previous elements. 92 string[] name; 93 /// 94 size_t nameLocation; 95 /// 96 Type type; 97 } 98 99 /// 100 struct ReferencedType 101 { 102 /// Referenced type name, might be longer than actual written name because of normalization of parents. 103 string name; 104 /// Location of name which will start right before the last identifier of the type in a dot chain. 105 size_t location; 106 } 107 108 /// Information about an interface or class 109 struct InterfaceDetails 110 { 111 /// Entire code of the file 112 const(char)[] code; 113 /// True if this is a class and therefore need to override methods using $(D override). 114 bool needsOverride; 115 /// Name of the interface or class. 116 string name; 117 /// Plain old variable fields in this container. 118 FieldDetails[] fields; 119 /// All methods defined in this container. 120 MethodDetails[] methods; 121 /// A list of nested types and locations defined in this interface/class. 122 TypeDetails[] types; 123 // reserved for future use with templates 124 string[] parents; 125 /// Name of all base classes or interfaces. Should use normalizedParents, 126 string[] normalizedParents; 127 /// Absolute code position after the colon where the corresponding parent name starts. 128 int[] parentPositions; 129 /// Range containing the starting and ending braces of the body. 130 size_t[2] blockRange; 131 /// A (name-based) sorted set of referenced types with first occurences of every type or alias not including built-in types, but including object.d types and aliases. 132 ReferencedType[] referencedTypes; 133 } 134 135 class InterfaceMethodFinder : AttributesVisitor 136 { 137 this(const(char)[] code, int targetPosition) 138 { 139 this.code = code; 140 details.code = code; 141 this.targetPosition = targetPosition; 142 } 143 144 override void visit(const StructDeclaration dec) 145 { 146 if (inTarget) 147 return; 148 else 149 super.visit(dec); 150 } 151 152 override void visit(const UnionDeclaration dec) 153 { 154 if (inTarget) 155 return; 156 else 157 super.visit(dec); 158 } 159 160 override void visit(const EnumDeclaration dec) 161 { 162 if (inTarget) 163 return; 164 else 165 super.visit(dec); 166 } 167 168 override void visit(const ClassDeclaration dec) 169 { 170 if (inTarget) 171 return; 172 173 auto c = context.save(); 174 context.pushContainer(ASTContext.ContainerAttribute.Type.class_, dec.name.text); 175 visitInterface(dec.name, dec.baseClassList, dec.structBody, true); 176 context.restore(c); 177 } 178 179 override void visit(const InterfaceDeclaration dec) 180 { 181 if (inTarget) 182 return; 183 184 auto c = context.save(); 185 context.pushContainer(ASTContext.ContainerAttribute.Type.interface_, dec.name.text); 186 visitInterface(dec.name, dec.baseClassList, dec.structBody, false); 187 context.restore(c); 188 } 189 190 private void visitInterface(const Token name, const BaseClassList baseClassList, 191 const StructBody structBody, bool needsOverride) 192 { 193 if (!structBody) 194 return; 195 if (inTarget) 196 return; // ignore nested 197 198 details.blockRange = [structBody.startLocation, structBody.endLocation + 1]; 199 if (targetPosition >= name.index && targetPosition < structBody.endLocation) 200 { 201 details.name = name.text; 202 if (baseClassList) 203 foreach (base; baseClassList.items) 204 { 205 if (!base.type2 || !base.type2.typeIdentifierPart 206 || !base.type2.typeIdentifierPart.identifierOrTemplateInstance) 207 continue; 208 // TODO: template support! 209 details.parents ~= astToString(base.type2); 210 details.normalizedParents ~= astToString(base.type2); 211 details.parentPositions ~= cast( 212 int) base.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier.index + 1; 213 } 214 details.needsOverride = needsOverride; 215 inTarget = true; 216 structBody.accept(new NestedTypeFinder(&details, details.name)); 217 super.visit(structBody); 218 inTarget = false; 219 } 220 } 221 222 override void visit(const FunctionDeclaration dec) 223 { 224 if (!inTarget) 225 return; 226 227 size_t[2] definitionRange = [dec.name.index, 0]; 228 size_t[2] blockRange; 229 230 if (dec.returnType !is null && dec.returnType.tokens.length > 0) 231 definitionRange[0] = dec.returnType.tokens[0].index; 232 233 if (dec.functionBody !is null && dec.functionBody.tokens.length > 0) 234 { 235 definitionRange[1] = dec.functionBody.tokens[0].index; 236 blockRange = [ 237 dec.functionBody.tokens[0].index, dec.functionBody.tokens[$ - 1].index + 1 238 ]; 239 } 240 else if (dec.parameters !is null && dec.parameters.tokens.length > 0) 241 definitionRange[1] = dec.parameters.tokens[$ - 1].index 242 + dec.parameters.tokens[$ - 1].text.length; 243 244 auto origBody = (cast() dec).functionBody; 245 const hasBody = !!origBody && origBody.missingFunctionBody is null; 246 auto origComment = (cast() dec).comment; 247 const implLevel = context.requiredImplementationLevel; 248 const optionalImplementation = implLevel == 1 && !hasBody; 249 const needsImplementation = implLevel == 9 || optionalImplementation; 250 (cast() dec).functionBody = null; 251 (cast() dec).comment = null; 252 scope (exit) 253 { 254 (cast() dec).functionBody = origBody; 255 (cast() dec).comment = origComment; 256 } 257 auto t = appender!string; 258 formatTypeTransforming(t, dec, &resolveType); 259 string method = context.localFormattedAttributes.chain([t.data.strip]) 260 .filter!(a => a.length > 0 && !a.among!("abstract", "final")).join(" "); 261 ArgumentInfo[] arguments; 262 if (dec.parameters) 263 foreach (arg; dec.parameters.parameters) 264 arguments ~= ArgumentInfo(astToString(arg), astToString(arg.type), arg.name.text); 265 string returnType = dec.returnType ? resolveType(astToString(dec.returnType)) : "void"; 266 267 // now visit to populate isNothrow, isNogc (before it would add to the localFormattedAttributes string) 268 // also fills in used types 269 super.visit(dec); 270 271 details.methods ~= MethodDetails(dec.name.text, method, returnType, arguments, context.isNothrowInContainer 272 || context.isNogcInContainer, hasBody, needsImplementation, 273 optionalImplementation, definitionRange, blockRange); 274 } 275 276 override void visit(const FunctionBody) 277 { 278 } 279 280 override void visit(const VariableDeclaration variable) 281 { 282 if (!inTarget) 283 return; 284 if (!variable.type) 285 return; 286 string type = astToString(variable.type); 287 auto isPrivate = context.protectionType == tok!"private"; 288 289 foreach (decl; variable.declarators) 290 details.fields ~= FieldDetails(decl.name.text, type, isPrivate); 291 292 if (variable.type) 293 variable.type.accept(this); // to fill in types 294 } 295 296 override void visit(const TypeIdentifierPart type) 297 { 298 if (!inTarget) 299 return; 300 301 if (type.identifierOrTemplateInstance && !type.typeIdentifierPart) 302 { 303 auto tok = type.identifierOrTemplateInstance.templateInstance 304 ? type.identifierOrTemplateInstance.templateInstance.identifier 305 : type.identifierOrTemplateInstance.identifier; 306 307 usedType(ReferencedType(tok.text, tok.index)); 308 } 309 310 super.visit(type); 311 } 312 313 alias visit = AttributesVisitor.visit; 314 315 protected void usedType(ReferencedType type) 316 { 317 // this is a simple sorted set insert 318 auto sorted = assumeSorted!"a.name < b.name"(details.referencedTypes).trisect(type); 319 if (sorted[1].length) 320 return; // exists already 321 details.referencedTypes.insertInPlace(sorted[0].length, type); 322 } 323 324 string resolveType(const(char)[] inType) 325 { 326 auto parts = inType.splitter('.'); 327 string[] best; 328 foreach (type; details.types) 329 if ((!best.length || type.name.length < best.length) && type.name.endsWith(parts)) 330 best = type.name; 331 332 if (best.length) 333 return best.join("."); 334 else 335 return inType.idup; 336 } 337 338 const(char)[] code; 339 bool inTarget; 340 int targetPosition; 341 InterfaceDetails details; 342 } 343 344 class NestedTypeFinder : ASTVisitor 345 { 346 this(InterfaceDetails* details, string start) 347 { 348 this.details = details; 349 this.nested = [start]; 350 } 351 352 override void visit(const StructDeclaration dec) 353 { 354 handleType(TypeDetails.Type.struct_, dec.name.text, dec.name.index, dec); 355 } 356 357 override void visit(const UnionDeclaration dec) 358 { 359 handleType(TypeDetails.Type.union_, dec.name.text, dec.name.index, dec); 360 } 361 362 override void visit(const EnumDeclaration dec) 363 { 364 handleType(TypeDetails.Type.enum_, dec.name.text, dec.name.index, dec); 365 } 366 367 override void visit(const ClassDeclaration dec) 368 { 369 handleType(TypeDetails.Type.class_, dec.name.text, dec.name.index, dec); 370 } 371 372 override void visit(const InterfaceDeclaration dec) 373 { 374 handleType(TypeDetails.Type.interface_, dec.name.text, dec.name.index, dec); 375 } 376 377 override void visit(const TemplateDeclaration dec) 378 { 379 handleType(TypeDetails.Type.template_, dec.name.text, dec.name.index, dec); 380 } 381 382 override void visit(const AliasDeclaration dec) 383 { 384 if (dec && dec.declaratorIdentifierList) 385 foreach (ident; dec.declaratorIdentifierList.identifiers) 386 details.types ~= TypeDetails(nested ~ ident.text, ident.index, TypeDetails.Type.alias_); 387 } 388 389 void handleType(T)(TypeDetails.Type type, string name, size_t location, T node) 390 { 391 pushNestedType(type, name, location); 392 super.visit(node); 393 popNestedType(); 394 } 395 396 override void visit(const FunctionBody) 397 { 398 } 399 400 alias visit = ASTVisitor.visit; 401 402 protected void pushNestedType(TypeDetails.Type type, string name, size_t index) 403 { 404 nested ~= name; 405 details.types ~= TypeDetails(nested, index, type); 406 } 407 408 protected void popNestedType() 409 { 410 nested.length--; 411 } 412 413 string[] nested; 414 InterfaceDetails* details; 415 } 416 417 void formatTypeTransforming(Sink, T)(Sink sink, T node, string delegate(const(char)[]) translateType, 418 bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWith = 4) 419 { 420 TypeTransformingFormatter!Sink formatter = new TypeTransformingFormatter!(Sink)(sink, 421 useTabs, style, indentWith); 422 formatter.translateType = translateType; 423 formatter.format(node); 424 } 425 426 /// 427 class TypeTransformingFormatter(Sink) : Formatter!Sink 428 { 429 string delegate(const(char)[]) translateType; 430 Appender!(char[]) tempBuffer; 431 bool useTempBuffer; 432 433 this(Sink sink, bool useTabs = false, IndentStyle style = IndentStyle.allman, uint indentWidth = 4) 434 { 435 super(sink, useTabs, style, indentWidth); 436 tempBuffer = appender!(char[]); 437 } 438 439 override void put(string s) 440 { 441 if (useTempBuffer) 442 tempBuffer.put(s); 443 else 444 super.put(s); 445 } 446 447 protected void flushTempBuffer() 448 { 449 if (!useTempBuffer || tempBuffer.data.empty) 450 return; 451 452 useTempBuffer = false; 453 put(translateType(tempBuffer.data)); 454 tempBuffer.clear(); 455 } 456 457 override void format(const TypeIdentifierPart type) 458 { 459 useTempBuffer = true; 460 461 if (type.dot) 462 { 463 put("."); 464 } 465 if (type.identifierOrTemplateInstance) 466 { 467 format(type.identifierOrTemplateInstance); 468 } 469 if (type.indexer) 470 { 471 flushTempBuffer(); 472 put("["); 473 format(type.indexer); 474 put("]"); 475 } 476 if (type.typeIdentifierPart) 477 { 478 put("."); 479 format(type.typeIdentifierPart); 480 } 481 else 482 { 483 flushTempBuffer(); 484 } 485 } 486 487 override void format(const IdentifierOrTemplateInstance identifierOrTemplateInstance) 488 { 489 with (identifierOrTemplateInstance) 490 { 491 format(identifier); 492 if (templateInstance) 493 { 494 flushTempBuffer(); 495 format(templateInstance); 496 } 497 } 498 } 499 500 alias format = Formatter!Sink.format; 501 }