Skip to content

Commit

Permalink
Check the class to which constructor args are passed
Browse files Browse the repository at this point in the history
Since IDEA may create a paradox with `$var = new SomeCls(); if ($var instanceof SomeOtherClass) {...}` causing keys to resolve as if args passed to `SomeCls` were passed to `SomeOtherCls`
  • Loading branch information
klesun committed Aug 30, 2018
1 parent 98b3c25 commit 678f4b5
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public ArrCtorRes(FuncCtx ctx)

public static Set<String> ideaTypeToFqn(@Nullable PhpType ideaType)
{
return new HashSet<>(opt(ideaType).def(PhpType.EMPTY).filterUnknown().filterNull().filterMixed().filter(PhpType.OBJECT).getTypes());
return new HashSet<>(opt(ideaType).def(PhpType.EMPTY).filterUnknown()
.filterNull().filterMixed().filter(PhpType.OBJECT).getTypes());
}

public static L<PhpClass> resolveIdeaTypeCls(PhpType ideaType, Project project)
Expand All @@ -50,8 +51,7 @@ public static L<PhpClass> resolveMtCls(MultiType mtArg, Project project)
if (resolved.size() == 0) {
// allow no namespace in php doc class references
PhpIndex idx = PhpIndex.getInstance(project);
L(mtArg.getIdeaType().filterUnknown().filterNull()
.filterMixed().filter(PhpType.OBJECT).getTypes()).flt(fqn -> !fqn.isEmpty())
L(ideaTypeToFqn(mtArg.getIdeaType())).flt(fqn -> !fqn.isEmpty())
.fch(clsName -> {
clsName = clsName.replaceAll("^\\\\", "");
resolved.addAll(idx.getClassesByName(clsName));
Expand Down
20 changes: 16 additions & 4 deletions src/org/klesun/deep_assoc_completion/resolvers/FieldRes.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.php.lang.psi.elements.Field;
import com.jetbrains.php.lang.psi.elements.Function;
import com.jetbrains.php.lang.psi.elements.Method;
import com.jetbrains.php.lang.psi.elements.PhpExpression;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.elements.impl.*;
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
import org.klesun.deep_assoc_completion.*;
import org.klesun.deep_assoc_completion.helpers.FuncCtx;
import org.klesun.deep_assoc_completion.helpers.MultiType;
Expand All @@ -18,6 +16,7 @@
import org.klesun.lang.Tls;

import java.util.List;
import java.util.Set;

public class FieldRes extends Lang
{
Expand Down Expand Up @@ -48,6 +47,17 @@ private static boolean areInSameScope(PsiElement a, PsiElement b)
return aFunc.equals(bFunc);
}

// when you do instanceof, IDEA type acquires the class, so it may
// happen that args passed to an instance as instance of one class
// could be used in some other instanceof-ed class if we don't do this check
private static boolean isSameClass(FuncCtx ctx, PhpClass fieldCls)
{
// return true if ctx class is empty or ctx class constructor is in fieldCls
// (if fieldCls is ctx class or ctx class inherits constructor from fieldCls)
Set<String> ctxFqns = ArrCtorRes.ideaTypeToFqn(ctx.clsIdeaType.def(PhpType.UNSET));
return ctxFqns.isEmpty() || ctxFqns.contains(fieldCls.getFQN());
}

public MultiType resolve(FieldReferenceImpl fieldRef)
{
L<DeepType> result = list();
Expand Down Expand Up @@ -105,6 +115,8 @@ public MultiType resolve(FieldReferenceImpl fieldRef)
.fop(toCast(PhpExpression.class))
.fop(ref -> opt(ctx.findExprType(ref)))
.fap(mt -> mt.getArgsPassedToCtor())
.flt(ctx -> opt(resolved.getContainingClass())
.map(cls -> isSameClass(ctx, cls)).def(true))
.wap(ctxs -> {
if (areInSameScope(fieldRef, assPsi)) {
ctxs.add(ctx);
Expand Down
80 changes: 40 additions & 40 deletions tests/src/DeepTest/ExactKeysUnitTest.php
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
<?php
namespace DeepTest;

use Lib\ParamValidation\DictP;
use Lib\ParamValidation\ListP;
use Lib\ParamValidation\StringP;

/**
* unlike UnitTest.php, this test not just checks that actual result has _at least_
* such keys, but it tests that it has _exactly_ such keys, without extras
*/
class ExactKeysUnitTest
{
private static function getPnrSchema()
{
return new DictP([], [
'recordLocator' => new StringP([], []),
'passengers' => new ListP([], ['elemType' => new DictP([], [
'lastName' => new StringP([], []),
'firstName' => new StringP([], []),
])]),
'itinerary' => new ListP([], ['elemType' => new ListP([], [
'from' => new StringP([], []),
'to' => new StringP([], []),
'date' => new StringP([], []),
])]),
]);
}

/** @param $pnr = ParamUtil::sample(static::getPnrSchema()) */
// public static function provideParamValidation($pnr)
// {
// // should suggest: 'recordLocator', 'passengers', 'itinerary'
// // should not suggest: 'elemType'
// $pnr[''];
// return [
// [$pnr, ['recordLocator', 'passengers', 'itinerary']],
// ];
// }
}
<?php
namespace DeepTest;

use Lib\ParamValidation\DictP;
use Lib\ParamValidation\ListP;
use Lib\ParamValidation\StringP;

/**
* unlike UnitTest.php, this test not just checks that actual result has _at least_
* such keys, but it tests that it has _exactly_ such keys, without extras
*/
class ExactKeysUnitTest
{
private static function getPnrSchema()
{
return new DictP([], [
'recordLocator' => new StringP([], ['pattern' => '/^[A-Z0-9]{6}$/']),
'passengers' => new ListP([], ['elemType' => new DictP([], [
'lastName' => new StringP([], []),
'firstName' => new StringP([], []),
])]),
'itinerary' => new ListP([], ['elemType' => new ListP([], [
'from' => new StringP([], []),
'to' => new StringP([], []),
'date' => new StringP([], []),
])]),
]);
}

/** @param $pnr = ParamUtil::sample(static::getPnrSchema()) */
public static function provideParamValidation($pnr)
{
// should suggest: 'recordLocator', 'passengers', 'itinerary'
// should not suggest: 'elemType'
$pnr[''];
return [
[$pnr, ['recordLocator', 'passengers', 'itinerary']],
];
}
}

0 comments on commit 678f4b5

Please sign in to comment.