Skip to content

Commit 697e7fb

Browse files
author
Tim Foley
authored
Attempt to fix lookup for members that "override" (shader-slang#1501)
Our current lookup process always finds *all* members of a type, which can include both an inherited member (e.g., from an `interface`) and one that logically overrides/implements it. If something downstream doesn't filter this result down and favor the derived member, then an ambiguity error will result. To date, this has mostly been a non-issue because we haven't emphasized inheritance, and the main case we did support (`struct` types implemented `interface` methods) gets disambiguated as part of overload resolution for function calls. Recent changes to support `property` declarations to `interface`s add the possibility for ambiguity between a "base" and "derived" declaration that can't rely on overload resolution for disambiguation. The approach in this PR is to add disambiguation logic to the other main place where the results of lookup get used. If a lookup result is being assigned to a variable, passed to a function, or otherwise used in a case where a value of a specific type is needed, it will be "coerced" to the desired type. This change makes it so that the first step in the coercion logic is to try to disambiguate the expression that is being coerced. In order to ensure that an overloaded expression can be detected and resolved even when just checking if coercion is possible, I needed to update the `canCoerce*()` functions to also take the expression that is being tested for coercibility, and not just its type. There is only one case (that I saw) where coercion checks were being made without an expression value available, and that case didn't actually need/want to handle overloading. In order to test the fixes here, I added logic to the `property`-in-`interface` test to make sure that the critical cases work as expected (references to a derived member using "dot syntax" and "implicit `this`" syntax). Alternatives Considered ----------------------- The first attempt at this fix took a simpler approach: I added the disambiguation logic as a post-process on member lookup. That is, given `obj.foo` I would take the `LookupResult` for `foo` and immediately try to filter it to include only the most-derived members. This approach has the major benefit of catching even more use cases of values (and thus helping to ensure that we don't spend forever chasing down more of these ambiguity errors), but it also has two critical problems: 1. If we only trigger disambiguation when looking up `obj.foo`, then we can't do anything to help when `foo` is looked up as an ordinary identifier, but is actually equivalent to `this.foo`. A full fix would require doing this disambiguation on *every* name lookup, which leads to the second issue: 2. It is important that for a method call like `obj.m(...)` we do *not* disambiguate when looking up `obj.m`, and instead let the overload resolution for the call resolve things. That choice is what makes it possible to call an inherited `m` declaration even when there is a derived `m` with a different signature. Issue (1) is covered by the test case that was added here, but we should probably have a test case for (2) to make sure we don't break that use case. Caveats ------- An important case that we don't solve in this PR is when the result of a lookup is captured in a variable without an explicit type: let f = obj.foo; That case also needs disambiguation, and should be addressed in a later change. A secondary issue is that our approach to prioritizing declarations during lookup is still quite naive. We really need a way for lookup to attach information about nesting of scopes to results (to be clear that results from inner scopes should be preferred over those from outer scopes), as well as have a robust mechanism for comparing the priority of members based on the inheritance graph of a type. This change doesn't do anything to make the situation better or worse.
1 parent ff2d490 commit 697e7fb

6 files changed

+32
-12
lines changed

source/slang/slang-check-conversion.cpp

+16-3
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ namespace Slang
8585
// we want to check for is whether a direct initialization
8686
// is possible (a type conversion exists).
8787
//
88-
return canCoerce(toType, fromExpr->type);
88+
return canCoerce(toType, fromExpr->type, fromExpr);
8989
}
9090

9191
bool SemanticsVisitor::_readValueFromInitializerList(
@@ -484,6 +484,18 @@ namespace Slang
484484
Expr* fromExpr,
485485
ConversionCost* outCost)
486486
{
487+
// If we are about to try and coerce an overloaded expression,
488+
// then we should start by trying to resolve the ambiguous reference
489+
// based on prioritization of the different candidates.
490+
//
491+
if( auto fromOverloadedExpr = as<OverloadedExpr>(fromExpr) )
492+
{
493+
auto resolvedExpr = maybeResolveOverloadedExpr(fromOverloadedExpr, LookupMask::Default, nullptr);
494+
495+
fromExpr = resolvedExpr;
496+
fromType = resolvedExpr->type;
497+
}
498+
487499
// An important and easy case is when the "to" and "from" types are equal.
488500
//
489501
if( toType->equals(fromType) )
@@ -756,6 +768,7 @@ namespace Slang
756768
bool SemanticsVisitor::canCoerce(
757769
Type* toType,
758770
Type* fromType,
771+
Expr* fromExpr,
759772
ConversionCost* outCost)
760773
{
761774
// As an optimization, we will maintain a cache of conversion results
@@ -795,7 +808,7 @@ namespace Slang
795808
toType,
796809
nullptr,
797810
fromType,
798-
nullptr,
811+
fromExpr,
799812
&cost);
800813

801814
if (outCost)
@@ -877,7 +890,7 @@ namespace Slang
877890
{
878891
// Can we convert at all?
879892
ConversionCost conversionCost;
880-
if(!canCoerce(toType, fromType, &conversionCost))
893+
if(!canCoerce(toType, fromType, nullptr, &conversionCost))
881894
return false;
882895

883896
// Is the conversion cheap enough to be done implicitly?

source/slang/slang-check-expr.cpp

-3
Original file line numberDiff line numberDiff line change
@@ -1963,9 +1963,6 @@ namespace Slang
19631963
return lookupMemberResultFailure(expr, baseType);
19641964
}
19651965

1966-
// TODO: need to filter for declarations that are valid to refer
1967-
// to in this context...
1968-
19691966
return createLookupResultExpr(
19701967
expr->name,
19711968
lookupResult,

source/slang/slang-check-impl.h

+1
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ namespace Slang
670670
bool canCoerce(
671671
Type* toType,
672672
Type* fromType,
673+
Expr* fromExpr,
673674
ConversionCost* outCost = 0);
674675

675676
TypeCastExpr* createImplicitCastExpr();

source/slang/slang-check-overload.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ namespace Slang
255255
if (context.mode == OverloadResolveContext::Mode::JustTrying)
256256
{
257257
ConversionCost cost = kConversionCost_None;
258-
if (!canCoerce(getType(m_astBuilder, valParamRef), arg->type, &cost))
258+
if (!canCoerce(getType(m_astBuilder, valParamRef), arg->type, arg, &cost))
259259
{
260260
success = false;
261261
}
@@ -344,7 +344,7 @@ namespace Slang
344344
if(!getType(m_astBuilder, param)->equals(argType))
345345
return false;
346346
}
347-
else if (!canCoerce(getType(m_astBuilder, param), argType, &cost))
347+
else if (!canCoerce(getType(m_astBuilder, param), argType, arg, &cost))
348348
{
349349
return false;
350350
}

tests/language-feature/properties/property-in-interface.slang

+10-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ struct MyCell : ICell
1919
struct YourCell : ICell
2020
{
2121
int value;
22+
23+
int getValue() { return value; }
2224
}
2325

2426
int helper<C : ICell>(C cell)
@@ -31,7 +33,14 @@ int test(int value)
3133
{
3234
MyCell myCell = { value+1 };
3335
YourCell yourCell = { value };
34-
return helper(myCell)*16 + helper(yourCell);
36+
37+
// Note: fetching `value` directly from `YourCell`
38+
// to confirm that member lookup is prioritizing
39+
// the concrete `YourCell::value` member of the inherited
40+
// abstract member `ICell::value`.
41+
//
42+
int f = (yourCell.value + yourCell.getValue()) / 2;
43+
return helper(myCell)*16 + helper(yourCell) + f;
3544
}
3645

3746
//TEST_INPUT:ubuffer(data=[0 1 2 3], stride=4):out,name=outputBuffer
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
21
2-
32
3-
43
4-
54
2+
33
3+
45
4+
57

0 commit comments

Comments
 (0)