-
-
Notifications
You must be signed in to change notification settings - Fork 150
/
CallImpl.kt
814 lines (684 loc) · 30.2 KB
/
CallImpl.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
package org.elixir_lang.psi.impl.call
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Computable
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.tree.TokenSet
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import org.elixir_lang.errorreport.Logger
import org.elixir_lang.mix.project.computeReadAction
import org.elixir_lang.psi.*
import org.elixir_lang.psi.call.Call
import org.elixir_lang.psi.call.StubBased
import org.elixir_lang.psi.call.arguments.None
import org.elixir_lang.psi.call.arguments.star.NoParentheses
import org.elixir_lang.psi.call.arguments.star.NoParenthesesOneArgument
import org.elixir_lang.psi.call.arguments.star.Parentheses
import org.elixir_lang.psi.call.name.Function.__MODULE__
import org.elixir_lang.psi.call.name.Module.KERNEL
import org.elixir_lang.psi.call.name.Module.stripElixirPrefix
import org.elixir_lang.psi.impl.*
import org.elixir_lang.psi.impl.ElixirPsiImplUtil.*
import org.elixir_lang.psi.operation.*
import org.elixir_lang.psi.qualification.Qualified
import org.elixir_lang.psi.qualification.Unqualified
import org.elixir_lang.psi.scope.ancestorTypeSpec
import org.elixir_lang.psi.scope.hasMapFieldOptionalityName
import org.elixir_lang.psi.stub.call.Stub
import org.elixir_lang.reference.Callable
import org.elixir_lang.reference.Callable.Companion.isBitStreamSegmentOption
import org.jetbrains.annotations.Contract
import java.util.*
import org.elixir_lang.psi.impl.macroChildCallList as psiElementToMacroChildCallList
fun Call.computeReference(): PsiReference? =
/* if the call is just the identifier for a module attribute reference, then don't return a Callable reference,
and instead let {@link #getReference(AtNonNumericOperation) handle it */
if (!this.isModuleAttributeNameElement() &&
// if a bitstring segment option then the option is a pseudo-function
!isBitStreamSegmentOption(this) && !this.isSlashInCaptureNameSlashArity()) {
val parent = parent
when {
parent is Type -> {
parent.leftOperand()?.let { parentLeftOperand ->
if (parentLeftOperand.isEquivalentTo(this)) {
val grandParent = parent.parent
val maybeArgument = if (grandParent is When) {
grandParent.parent
} else {
grandParent
}
(maybeArgument as? ElixirNoParenthesesOneArgument)?.let { argument ->
(argument.parent as? AtUnqualifiedNoParenthesesCall<*>)?.let { moduleAttribute ->
val name = moduleAttributeName(moduleAttribute)
if (name == "@spec") {
org.elixir_lang.reference.CallDefinitionClause(this, moduleAttribute)
} else {
null
}
}
}
} else {
null
}
}
?: computeCallableReference()
}
parent.isSlashInCaptureNameSlashArity() -> null
else -> computeCallableReference()
}
} else {
null
}
private fun PsiElement.isCaptureNonNumericOperation(): Boolean =
when (this) {
is ElixirMatchedLessThanOnePointSixCaptureNonNumericOperation,
is ElixirMatchedGreaterThanOrEqualToOnePointSixCaptureNonNumericOperation,
is ElixirUnmatchedLessThanOnePointSixCaptureNonNumericOperation,
is ElixirUnmatchedGreaterThanOrEqualToOnePointSixCaptureNonNumericOperation ->
true
else ->
false
}
private fun PsiElement.isSlashInCaptureNameSlashArity(): Boolean =
if (this is Infix &&
(this is ElixirMatchedMultiplicationOperation || this is ElixirUnmatchedMultiplicationOperation)) {
val operator = org.elixir_lang.psi.operation.Normalized.operator(this)
val divisionOperatorChildren = operator.node.getChildren(TokenSet.create(ElixirTypes.DIVISION_OPERATOR))
if (divisionOperatorChildren.isNotEmpty()) {
val rightOperand = org.elixir_lang.psi.operation.infix.Normalized.rightOperand(this)?.stripAccessExpression()
if (rightOperand is ElixirDecimalWholeNumber) {
val parent = this.parent
parent.isCaptureNonNumericOperation()
} else {
false
}
} else {
false
}
} else {
false
}
private fun Call.computeCallableReference(): PsiReference? =
if (Callable.isDefiner(this)) {
Callable.definer(this)
} else {
val ancestorTypeSpec = this.ancestorTypeSpec()
if (ancestorTypeSpec != null) {
if (this.hasMapFieldOptionalityName()) {
null
} else {
org.elixir_lang.reference.Type(ancestorTypeSpec, this)
}
} else {
Callable(this)
}
}
/**
* The outer most arguments
*
* @return [Call.primaryArguments]
*/
fun Call.finalArguments(): Array<PsiElement>? = try {
(secondaryArguments() ?: primaryArguments())?.map { it!! }?.toTypedArray()
} catch (e: NullPointerException) {
Logger.error(this.javaClass, "NullPointerException getting Call.finalArguments()", this);
null
}
fun Call.getReference(): PsiReference? =
CachedValuesManager.getCachedValue(this) {
CachedValueProvider.Result.create(computeReference(), this)
}
/**
* The value of the keyword argument with the given keywordKeyText.
*
* @param this@keywordArgument call to seach for the keyword argument.
* @param keywordKeyText the text of the key, such as `"do"`
* @return the keyword value `PsiElement` if `call` has [ElixirPsiImplUtil.keywordArguments]
* and there is a [] for `keywordKeyText`.
*/
fun Call.keywordArgument(keywordKeyText: String): PsiElement? = keywordArguments()?.keywordValue(keywordKeyText)
/**
* The keyword arguments for `call`.
* @param this@keywordArguments call to search for keyword arguments.
* @return the final element of the [ElixirPsiImplUtil.finalArguments] of `` if they are a
* [QuotableKeywordList]; otherwise, `null`.
*/
fun Call.keywordArguments(): QuotableKeywordList? {
val finalArguments = this.finalArguments()
var keywordArguments: QuotableKeywordList? = null
if (finalArguments != null) {
val finalArgumentCount = finalArguments.size
if (finalArgumentCount > 0) {
val potentialKeywords = finalArguments[finalArgumentCount - 1]
if (potentialKeywords is QuotableKeywordList) {
keywordArguments = potentialKeywords
} else if (potentialKeywords is ElixirAccessExpression) {
val accessExpressionChild = potentialKeywords.stripAccessExpression()
if (accessExpressionChild is ElixirList) {
val listChildren = accessExpressionChild.children
if (listChildren.size == 1) {
val listChild = listChildren[0]
if (listChild is QuotableKeywordList) {
keywordArguments = listChild
}
}
}
}
}
}
return keywordArguments
}
fun Call.macroChildCalls(): Array<Call> {
val childCallList = macroChildCallList()
return childCallList.toTypedArray()
}
fun <R> Call.foldChildrenWhile(initial: R, operation: (PsiElement, acc: R) -> AccumulatorContinue<R>): AccumulatorContinue<R> {
val doBlock = doBlock
return if (doBlock != null) {
doBlock.stab?.let { stab ->
val stabChildren = stab.children
if (stabChildren.size == 1) {
val stabChild = stabChildren[0]
if (stabChild is ElixirStabBody) {
stabChild.foldChildrenWhile(initial, operation)
} else {
null
}
} else {
null
}
}
} else { // one liner version with `do:` keyword argument
val finalArguments = finalArguments()!!
assert(finalArguments.isNotEmpty())
val potentialKeywords = finalArguments[finalArguments.size - 1]
if (potentialKeywords is QuotableKeywordList) {
val quotableKeywordPairList = potentialKeywords.quotableKeywordPairList()
val firstQuotableKeywordPair = quotableKeywordPairList[0]
val keywordKey = firstQuotableKeywordPair.keywordKey
if (keywordKey.text == "do") {
firstQuotableKeywordPair.keywordValue.foldChildrenWhile(initial, operation)
} else {
null
}
} else {
null
}
} ?: AccumulatorContinue(initial, true)
}
fun Call.macroChildCallList(): List<Call> {
var childCallList: List<Call>? = null
val doBlock = doBlock
if (doBlock != null) {
val stab = doBlock.stab
if (stab != null) {
val stabChildren = stab.children
if (stabChildren.size == 1) {
val stabChild = stabChildren[0]
if (stabChild is ElixirStabBody) {
childCallList = stabChild.psiElementToMacroChildCallList()
}
}
}
} else { // one liner version with `do:` keyword argument
val finalArguments = finalArguments()!!
assert(finalArguments.isNotEmpty())
val potentialKeywords = finalArguments[finalArguments.size - 1]
if (potentialKeywords is QuotableKeywordList) {
val quotableKeywordPairList = potentialKeywords.quotableKeywordPairList()
val firstQuotableKeywordPair = quotableKeywordPairList[0]
val keywordKey = firstQuotableKeywordPair.keywordKey
if (keywordKey.text == "do") {
val keywordValue = firstQuotableKeywordPair.keywordValue
if (keywordValue is Call) {
val childCall = keywordValue as Call
childCallList = listOf(childCall)
}
}
}
}
if (childCallList == null) {
childCallList = emptyList()
}
return childCallList
}
fun Call.macroChildCallSequence(): Sequence<Call> = this.macroChildCallList().asSequence()
@Contract(pure = true)
fun Call.macroDefinitionClauseForArgument(): Call? {
var macroDefinitionClause: Call? = null
val parent = parent
if (parent is ElixirMatchedWhenOperation) {
val grandParent = parent.getParent()
if (grandParent is ElixirNoParenthesesOneArgument) {
val greatGrandParent = grandParent.getParent()
if (greatGrandParent is Call) {
if (CallDefinitionClause.isMacro(greatGrandParent)) {
macroDefinitionClause = greatGrandParent
}
}
}
}
return macroDefinitionClause
}
fun Call.maybeModularNameToModular(useCall: Call?): Call? =
if (isCalling(KERNEL, __MODULE__, 0)) {
org.elixir_lang.psi.__MODULE__
.reference(__MODULE__Call = this, useCall = useCall)
.resolve()
?.let { it as Call? }?.let { resolved ->
if (org.elixir_lang.psi.stub.type.call.Stub.isModular(resolved)) {
resolved
} else {
null
}
}
} else {
null
}
object CallImpl {
@Contract(pure = true)
@JvmStatic
fun functionName(call: Call): String? =
call.functionNameElement()?.let { element ->
computeReadAction(Computable<String> { element.text })
}
/**
* @return `null` because the `IDENTIFIER`, `foo` in `@foo 1` is not the local name of a function, but the name of a
* Module attribute.
*/
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun functionNameElement(
@Suppress("UNUSED_PARAMETER") atUnqualifiedNoParenthesesCall: AtUnqualifiedNoParenthesesCall<*>
): PsiElement? = null
/**
* @return `null` because the expression before the `.` is a variable name and not a function name.
*/
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun functionNameElement(@Suppress("UNUSED_PARAMETER") dotCall: DotCall<*>): PsiElement? = null
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun functionNameElement(@Suppress("UNUSED_PARAMETER") notIn: NotIn): PsiElement? = null
@Contract(pure = true)
@JvmStatic
fun functionNameElement(operation: Operation): PsiElement = operation.operator()
@JvmStatic
fun functionNameElement(qualified: Qualified): PsiElement = qualified.relativeIdentifier
@Contract(pure = true)
@JvmStatic
fun functionNameElement(unqualified: Unqualified): PsiElement = unqualified.firstChild
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun getDoBlock(
@Suppress("UNUSED_PARAMETER")
unqualifiedNoParenthesesManyArgumentsCall: ElixirUnqualifiedNoParenthesesManyArgumentsCall
): ElixirDoBlock? =
null
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun getDoBlock(@Suppress("UNUSED_PARAMETER") notIn: NotIn): ElixirDoBlock? = null
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun getDoBlock(@Suppress("UNUSED_PARAMETER") operation: Operation): ElixirDoBlock? = null
@Contract(pure = true)
@JvmStatic
fun getDoBlock(@Suppress("UNUSED_PARAMETER") matchedCall: MatchedCall): ElixirDoBlock? = null
@JvmStatic
fun hasDoBlockOrKeyword(call: Call): Boolean =
call.doBlock != null || call.keywordArgument("do") != null
@JvmStatic
fun hasDoBlockOrKeyword(stubBased: StubBased<Stub<*>>): Boolean =
stubBased.stub?.hasDoBlockOrKeyword() ?: hasDoBlockOrKeyword(stubBased as Call)
/**
* Whether the `call` is calling the given `functionName` in the `resolvedModuleName` with any arity
*
* @param call the call element
* @param resolvedModuleName the expected [Call.resolvedModuleName]
* @param functionName the expected [Call.functionName]
* @return `true` if the `call` has non-`null` [Call.resolvedModuleName] that equals
* `resolvedModuleName` and has non-`null` [Call.functionName] that equals
* `functionName`; otherwise, `false`.
*/
@JvmStatic
fun isCalling(call: Call,
resolvedModuleName: String,
functionName: String): Boolean {
val callResolvedModuleName = call.resolvedModuleName()
val callFunctionName = call.functionName()
return callResolvedModuleName != null && callResolvedModuleName == resolvedModuleName &&
callFunctionName != null && callFunctionName == functionName
}
/**
* Whether the `call` is calling the given `functionName` in the `resolvedModuleName` with the
* `resolvedFinalArity`
*
* @param call the call element
* @param resolvedModuleName the expected [Call.resolvedModuleName]
* @param functionName the expected [Call.functionName]
* @param resolvedFinalArity the expected [Call.resolvedFinalArity]
* @return `true` if the `call` has non-`null` [Call.resolvedModuleName] that equals
* `resolvedModuleName` and has non-`null` [Call.functionName] that equals
* `functionName` and the [Call.resolvedFinalArity]; otherwise, `false`.
*/
@JvmStatic
fun isCalling(call: Call,
resolvedModuleName: String,
functionName: String,
resolvedFinalArity: Int): Boolean =
call.isCalling(resolvedModuleName, functionName) && call.resolvedFinalArity() == resolvedFinalArity
/**
* Whether `call` is of the named macro.
*
*
* Differs from [ElixirPsiImplUtil.isCallingMacro] because no arity is necessary,
* which is useful for special forms, which don't have a set arity. (That's actually why they need to be special
* forms since Erlang/Elixir doesn't support variable arity functions otherwise.)
*
* @param call the call element
* @param resolvedModuleName the expected [Call.resolvedModuleName]
* @param functionName the expected [Call.functionName]
* @return `true` if all arguments match and [Call.getDoBlock] is not `null`; `false`.
*/
@JvmStatic
fun isCallingMacro(call: Call,
resolvedModuleName: String,
functionName: String): Boolean =
call.isCalling(resolvedModuleName, functionName) && call.hasDoBlockOrKeyword()
/**
* Whether `call` is of the named macro.
*
*
* Differs from [ElixirPsiImplUtil.isCalling] because this function ensures there
* is a `do` block. If the macro can be called without a `do` block, then
* [ElixirPsiImplUtil.isCalling] should be called instead.
*
* @param call the call element
* @param resolvedModuleName the expected [Call.resolvedModuleName]
* @param functionName the expected [Call.functionName]
* @param resolvedFinalArity the expected [Call.resolvedFinalArity]
* @return `true` if all arguments match and [Call.getDoBlock] is not `null`; `false`.
*/
@Contract(pure = true)
@JvmStatic
fun isCallingMacro(call: Call,
resolvedModuleName: String,
functionName: String,
resolvedFinalArity: Int): Boolean =
call.isCalling(resolvedModuleName, functionName, resolvedFinalArity) && call.hasDoBlockOrKeyword()
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun moduleName(@Suppress("UNUSED_PARAMETER")
atUnqualifiedNoParenthesesCall: AtUnqualifiedNoParenthesesCall<*>): String? =
// Always null because it's unqualified.
null
// Always null because anonymous
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun moduleName(@Suppress("UNUSED_PARAMETER") dotCall: DotCall<*>): String? = null
// Always null because it's unqualified.
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun moduleName(@Suppress("UNUSED_PARAMETER") notIn: NotIn): String? = null
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun moduleName(@Suppress("UNUSED_PARAMETER") operation: Operation): String? = null
// Always null because it's unqualified.
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun moduleName(@Suppress("UNUSED_PARAMETER") unqualified: Unqualified): String? = null
// TODO handle more complex qualifiers besides Aliases
@Contract(pure = true)
@JvmStatic
fun moduleName(qualified: Qualified): String = computeReadAction(Computable<String> { qualified.firstChild.text })
@Contract(pure = true)
@JvmStatic
fun primaryArguments(dotCall: DotCall<*>): Array<PsiElement> = dotCall.parenthesesArgumentsList[0].arguments()
@Contract(pure = true)
@JvmStatic
fun primaryArguments(infix: Infix): Array<PsiElement> {
val children = infix.children
val operatorIndex = Normalized.operatorIndex(children)
val leftOperand = org.elixir_lang.psi.operation.infix.Normalized.leftOperand(children, operatorIndex)
val rightOperand = org.elixir_lang.psi.operation.infix.Normalized.rightOperand(children, operatorIndex)
return if (leftOperand != null) {
if (rightOperand != null) {
arrayOf<PsiElement>(leftOperand, rightOperand)
} else {
arrayOf<PsiElement>(leftOperand)
}
} else {
if (rightOperand != null) {
arrayOf<PsiElement>(rightOperand)
} else {
emptyArray()
}
}
}
@Contract(pure = true)
@JvmStatic
fun primaryArguments(unqualifiedNoParenthesesManyArgumentsCall: ElixirUnqualifiedNoParenthesesManyArgumentsCall): Array<PsiElement> {
val arguments = unqualifiedNoParenthesesManyArgumentsCall.noParenthesesStrict
return if (arguments != null) {
arguments.arguments()
} else {
/* noParenthesesManyArguments is a private rule, so when noParenthesesStrict is not present, then the
noParenthesesManyArguments are direct children, but so is he identifier, so the identifier needs to be
ignored */
val children = unqualifiedNoParenthesesManyArgumentsCall.children
assert(children[0] is ElixirIdentifier)
Arrays.copyOfRange(children, 1, children.size)
}
}
@Contract(pure = true)
@JvmStatic
fun primaryArguments(@Suppress("UNUSED_PARAMETER") none: None): Array<PsiElement>? = null
@Contract(pure = true)
@JvmStatic
fun primaryArguments(notIn: NotIn): Array<PsiElement?> {
val children = notIn.children
val leftOperand = org.elixir_lang.psi.operation.not_in.Normalized.leftOperand(children)
val rightOperand = org.elixir_lang.psi.operation.not_in.Normalized.rightOperand(children)
return arrayOf(leftOperand, rightOperand)
}
@Contract(pure = true)
@JvmStatic
fun primaryArguments(noParenthesesOneArgument: NoParenthesesOneArgument): Array<PsiElement> =
noParenthesesOneArgument.noParenthesesOneArgument.arguments()
@Contract(pure = true)
@JvmStatic
fun primaryArguments(parentheses: Parentheses): Array<PsiElement> {
val matchedParenthesesArguments = parentheses.matchedParenthesesArguments
val parenthesesArgumentsList = matchedParenthesesArguments.parenthesesArgumentsList
val primaryParenthesesArguments = parenthesesArgumentsList[0]
return primaryParenthesesArguments.arguments()
}
@Contract(pure = true)
@JvmStatic
fun primaryArguments(prefix: Prefix): Array<PsiElement?> = arrayOf(prefix.operand())
@Contract(pure = true)
@JvmStatic
fun primaryArity(call: Call): Int? = call.primaryArguments()?.size
@Contract(pure = true)
@JvmStatic
fun secondaryArguments(dotCall: DotCall<*>): Array<PsiElement>? {
val parenthesesArgumentsList = dotCall.parenthesesArgumentsList
return if (parenthesesArgumentsList.size < 2) {
null
} else {
val parenthesesArguments = parenthesesArgumentsList[1]
parenthesesArguments.arguments()
}
}
@Contract(pure = true)
@JvmStatic
fun secondaryArguments(@Suppress("UNUSED_PARAMETER") infix: Infix): Array<PsiElement>? = null
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun secondaryArguments(@Suppress("UNUSED_PARAMETER") none: None): Array<PsiElement>? = null
@Contract(pure = true)
@JvmStatic
fun secondaryArguments(@Suppress("UNUSED_PARAMETER") notIn: NotIn): Array<PsiElement>? = null
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun secondaryArguments(@Suppress("UNUSED_PARAMETER") noParentheses: NoParentheses): Array<PsiElement>? = null
@Contract(pure = true)
@JvmStatic
fun secondaryArguments(parentheses: Parentheses): Array<PsiElement>? {
val matchedParenthesesArguments = parentheses.matchedParenthesesArguments
val parenthesesArgumentsList = matchedParenthesesArguments.parenthesesArgumentsList
return if (parenthesesArgumentsList.size < 2) {
null
} else {
val parenthesesArguments = parenthesesArgumentsList[1]
parenthesesArguments.arguments()
}
}
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun secondaryArguments(@Suppress("UNUSED_PARAMETER") prefix: Prefix): Array<PsiElement>? = null
@Contract(pure = true)
@JvmStatic
fun secondaryArity(call: Call): Int? = call.secondaryArguments()?.size
// TODO handle resolving module name from module attribute's declaration
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun resolvedModuleName(
@Suppress("UNUSED_PARAMETER") atUnqualifiedNoParenthesesCall: AtUnqualifiedNoParenthesesCall<*>
): String? = null
// TODO handle resolving module name from any capture from variable declaration
@Contract(pure = true, value = "_ -> null")
@JvmStatic
fun resolvedModuleName(@Suppress("UNUSED_PARAMETER") dotCall: DotCall<*>): String? = null
/* TODO handle resolving module name from imports. Assume KERNEL for now, but some are actually from Bitwise */
@Contract(pure = true)
@JvmStatic
fun resolvedModuleName(@Suppress("UNUSED_PARAMETER") infix: Infix): String = KERNEL
@Contract(pure = true)
@JvmStatic
fun resolvedModuleName(@Suppress("UNUSED_PARAMETER") notIn: NotIn): String = KERNEL
/* TODO handle resolving module name from imports. Assume KERNEL for now. */
@Contract(pure = true)
@JvmStatic
fun resolvedModuleName(@Suppress("UNUSED_PARAMETER") prefix: Prefix): String = KERNEL
@Suppress("UNCHECKED_CAST")
@JvmStatic
fun resolvedModuleName(qualified: org.elixir_lang.psi.call.qualification.Qualified): String =
(qualified as? org.elixir_lang.psi.call.StubBased<Stub<*>>)?.stub?.resolvedFunctionName() ?:
stripElixirPrefix(qualified.moduleName())
@Suppress("UNCHECKED_CAST")
@JvmStatic
fun resolvedModuleName(unqualified: Unqualified): String =
(unqualified as? org.elixir_lang.psi.call.StubBased<Stub<*>>)?.let { stubBased ->
ApplicationManager.getApplication().runReadAction(Computable {
stubBased.stub
})?.resolvedModuleName()
} ?: KERNEL
// TODO handle `import`s and determine whether actually a local variable
@Contract(pure = true)
@JvmStatic
fun resolvedModuleName(
@Suppress("UNUSED_PARAMETER") unqualifiedNoArgumentsCall: UnqualifiedNoArgumentsCall<*>
): String = KERNEL
@Contract(pure = true)
@JvmStatic
fun resolvedPrimaryArity(call: Call): Int? {
val primaryArity = call.primaryArity()
var resolvedPrimaryArity = primaryArity
/* do block and piping attach to the outer most parentheses, only count do block and piping for primary if there
are no secondary. */
if (call.secondaryArity() == null) {
if (call.doBlock != null) {
if (primaryArity == null) {
resolvedPrimaryArity = 1
} else {
resolvedPrimaryArity!!
resolvedPrimaryArity += 1
}
}
val parent = computeReadAction(Computable<PsiElement> { call.parent })
if (parent.isPipe()) {
val parentPipeOperation = parent as Arrow
val pipedInto = parentPipeOperation.rightOperand()
/* only the right operand has its arity increased because it is the operand that has the output of the
left operand prepended to its arguments */
if (pipedInto != null && call.isEquivalentTo(pipedInto)) {
if (primaryArity == null) {
resolvedPrimaryArity = 1
} else {
resolvedPrimaryArity!!
resolvedPrimaryArity += 1
}
}
}
}
return resolvedPrimaryArity
}
@Contract(pure = true)
@JvmStatic
fun resolvedFinalArity(call: Call): Int = call.resolvedSecondaryArity() ?: call.resolvedPrimaryArity() ?: 0
@Contract(pure = true)
@JvmStatic
fun resolvedFinalArity(stubBased: org.elixir_lang.psi.call.StubBased<Stub<*>>): Int =
stubBased.stub?.resolvedFinalArity() ?: resolvedFinalArity(stubBased as Call) ?: 0
@Contract(pure = true)
@JvmStatic
fun resolvedFinalArityRange(call: Call): IntRange =
call.finalArguments()?.let { finalArguments ->
val defaultCount = finalArguments.defaultArgumentCount()
val maximum = finalArguments.size
val minimum = maximum - defaultCount
IntRange(minimum, maximum)
} ?: IntRange(0, 0)
@Contract(pure = true)
@JvmStatic
fun resolvedSecondaryArity(call: Call): Int? =
call.secondaryArity()?.let { secondaryArity ->
if (call.doBlock != null) {
secondaryArity + 1
} else {
secondaryArity
}
}
// Private Functions
/**
* The number of arguments that have defaults.
*
* @param this@defaultArgumentCount arguments to a definition call
*/
private fun Array<PsiElement>.defaultArgumentCount(): Int = count { it.isDefaultArgument() }
/**
* Whether the given element presents a default argument (with `\\` in it.
*
* @param this@isDefaultArgument an argument to a [Call]
* @return `true` if in match operation with `\\` operator; otherwise, `false`.
*/
private fun PsiElement.isDefaultArgument(): Boolean {
var defaultArgument = false
if (this is InMatch) {
val operation = this as Operation
if (operation.operator().text.trim { it <= ' ' } == DEFAULT_OPERATOR) {
defaultArgument = true
}
}
return defaultArgument
}
/**
* Whether the `arrow` is a pipe operation.
*
* @param this@isPipe the parent (or futher ancestor of a [Call] that may be piped.
* @return `` true if `arrow` is using the `"|>"` operator token.
*/
private fun Arrow.isPipe(): Boolean =
operator().node.getChildren(ARROW_OPERATOR_TOKEN_SET).let { arrowOperatorChildren ->
arrowOperatorChildren.size == 1 && arrowOperatorChildren[0].text == "|>"
}
/**
* Whether the `callAncestor` is a pipe operation.
*
* @param this@isPipe the parent (or further ancestor) of a [Call] that may be piped
* @return `` true if `callAncestor` is an [Arrow] using the `"|>"` operator token.
*/
private fun PsiElement.isPipe(): Boolean = (this as? Arrow)?.isPipe() ?: false
}