Skip to content

Commit bc0d0f9

Browse files
author
Tim Foley
authored
Clean up the way that lookup "through" a base type is encoded (shader-slang#1519)
* Clean up the way that lookup "through" a base type is encoded In order to undestand this change, it is important to undestand how lookup through base interfaces works prior to this change. In order to understand *that* it helps to be reminded of how inheritance relationships get encoded in the AST. Suppose the user writes: struct Base { int val; } struct Derived : Base { ... } ... Derived d = ...; int v = d.val; The question is how an expression like `d.val` gets semantically checked, and how it is encoded into the IR after semantic checking. You might assume it gets checked and encoded so that we end up with: int v = ((Base) d).val; and that seems like it should Just Work... so of course that isn't what Slang has been doing. Instead, we relied on the fact that the inheritance relationship `Derived : Base` is represented as an `InheritanceDecl` member of the `Derived` type, and we ended up checking the code into something like: int v = d.<anonymous>.val; where `<anonymous>` stands in for the name of the `InheritanceDecl` that represents inheritance from `Base`. This design choice makes a limited amount of sense when you consider how inheritance would typically be lowered to a C-like output language: // struct Derived : Base { ... } // => struct Derived { Base base; ... } The problem with that encoding is that it really doesn't make sense for almost any other scenario. In particular, if you have a generic type parameter `T` that was constrianed with `T : ISomething`, then the constraint isn't even technically a *member* of the type parameter `T`, so expressing thing as a member reference in the AST is completely incorrect. Unfortunately, by the time it was clear that we needed something better, a bunch of implementation work was done based on the existing representation. This change tries to clean things up so that lookup of a super-type member through a value of a sub-type does the obvious thing: cast the value to the super-type and then look up the member (as in `((Base) d).val`). The core of the change is that in lookup, instead of creating `Constraint` breadcrumbs whenever we are looking up in a super-type (with a reference to the `TypeConstraintDecl` being used) we instead use `SuperType` breadcrumbs (with a reference to a `SubtypeWitness`). Then when we create the expression from a `LookupResultItem`, we translate any `SuperType` breadcrumbs into `CastToSuperTypeExpr`s (an expression type that already existed). This change also adds support for lookup through the `This` type in the context of an interface, and in order for that to work we need a new kind of subtype witness to represent the knowledge that a `This` type is a subtype of the enclosing interface. Making that work forces us to change the representation of `TransitiveSubtypeWitness` so that it takes a pair of subtype witnesses (and not one subtype witness plus one `TypeConstraintDecl`). For the most part this is a small change, but it raises the possibility that some pieces of the code aren't going to be robust against all possible shapes of subtype witnesses. The IR lowering logic has relied on the weird `d.<anonymous>` representation in order to ensure that when looking up interface members we weren't always casting to the interface type (which would create a `makeExistential` instruction), and then calling using that. Basically, the IR lowering would ignore the `d.<anonymous>` part and just emit `d`, but we can't do that for `((Base) d)` or `((IThing) d)` because whehter or not we should actually perform the cast depends on context. For now we solve that problem by adding specific logic to ignore up-casts to interface types when they appear in member expressions or method calls. A more robust solution might be needed down the line, but this seems to work in practice. All of this work is cleanup that I found was needed in order to make `extension`s of `interface` types workable. * fixup: disable an incorrect test
1 parent c936433 commit bc0d0f9

10 files changed

+182
-88
lines changed

source/slang/slang-ast-serialize.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1624,7 +1624,7 @@ SlangResult ASTSerialReader::load(const uint8_t* data, size_t dataCount, ASTBuil
16241624
{
16251625
typedef LookupResultItem::Breadcrumb Breadcrumb;
16261626

1627-
auto breadcrumb = new LookupResultItem::Breadcrumb(Breadcrumb::Kind::Member, DeclRef<Decl>(), nullptr);
1627+
auto breadcrumb = new LookupResultItem::Breadcrumb(Breadcrumb::Kind::Member, DeclRef<Decl>(), nullptr, nullptr);
16281628
m_scope.add(breadcrumb);
16291629
m_objects[i] = breadcrumb;
16301630
break;

source/slang/slang-ast-support-types.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ namespace Slang
11181118
// `T` is a subtype of some other type `U`, so that
11191119
// lookup was able to find a member through type `U`
11201120
// instead.
1121-
Constraint,
1121+
SuperType,
11221122

11231123
// The lookup process considered a member of an
11241124
// enclosing type as being in scope, so that any
@@ -1154,18 +1154,22 @@ namespace Slang
11541154
//
11551155
DeclRef<Decl> declRef;
11561156

1157+
Val* val = nullptr;
1158+
11571159
// The next implicit step that the lookup process took to
11581160
// arrive at a final value.
11591161
RefPtr<Breadcrumb> next;
11601162

11611163
Breadcrumb(
11621164
Kind kind,
11631165
DeclRef<Decl> declRef,
1166+
Val* val,
11641167
RefPtr<Breadcrumb> next,
11651168
ThisParameterMode thisParameterMode = ThisParameterMode::Default)
11661169
: kind(kind)
11671170
, thisParameterMode(thisParameterMode)
11681171
, declRef(declRef)
1172+
, val(val)
11691173
, next(next)
11701174
{}
11711175
};

source/slang/slang-ast-val.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ bool TransitiveSubtypeWitness::_equalsValOverride(Val* val)
363363
return sub->equals(otherWitness->sub)
364364
&& sup->equals(otherWitness->sup)
365365
&& subToMid->equalsVal(otherWitness->subToMid)
366-
&& midToSup.equals(otherWitness->midToSup);
366+
&& midToSup->equalsVal(otherWitness->midToSup);
367367
}
368368

369369
Val* TransitiveSubtypeWitness::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int * ioDiff)
@@ -373,7 +373,7 @@ Val* TransitiveSubtypeWitness::_substituteImplOverride(ASTBuilder* astBuilder, S
373373
Type* substSub = as<Type>(sub->substituteImpl(astBuilder, subst, &diff));
374374
Type* substSup = as<Type>(sup->substituteImpl(astBuilder, subst, &diff));
375375
SubtypeWitness* substSubToMid = as<SubtypeWitness>(subToMid->substituteImpl(astBuilder, subst, &diff));
376-
DeclRef<Decl> substMidToSup = midToSup.substituteImpl(astBuilder, subst, &diff);
376+
SubtypeWitness* substMidToSup = as<SubtypeWitness>(midToSup->substituteImpl(astBuilder, subst, &diff));
377377

378378
// If nothing changed, then we can bail out early.
379379
if (!diff)
@@ -416,7 +416,7 @@ String TransitiveSubtypeWitness::_toStringOverride()
416416
sb << "TransitiveSubtypeWitness(";
417417
sb << this->subToMid->toString();
418418
sb << ", ";
419-
sb << this->midToSup.toString();
419+
sb << this->midToSup->toString();
420420
sb << ")";
421421
return sb.ProduceString();
422422
}
@@ -426,7 +426,7 @@ HashCode TransitiveSubtypeWitness::_getHashCodeOverride()
426426
auto hash = sub->getHashCode();
427427
hash = combineHash(hash, sup->getHashCode());
428428
hash = combineHash(hash, subToMid->getHashCode());
429-
hash = combineHash(hash, midToSup.getHashCode());
429+
hash = combineHash(hash, midToSup->getHashCode());
430430
return hash;
431431
}
432432

source/slang/slang-ast-val.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class TransitiveSubtypeWitness : public SubtypeWitness
154154
SubtypeWitness* subToMid = nullptr;
155155

156156
// Witness that `mid : sup`
157-
DeclRef<Decl> midToSup;
157+
SubtypeWitness* midToSup = nullptr;
158158

159159
// Overrides should be public so base classes can access
160160
bool _equalsValOverride(Val* val);
@@ -200,4 +200,9 @@ class TaggedUnionSubtypeWitness : public SubtypeWitness
200200
Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff);
201201
};
202202

203+
class ThisTypeSubtypeWitness : public SubtypeWitness
204+
{
205+
SLANG_CLASS(ThisTypeSubtypeWitness)
206+
};
207+
203208
} // namespace Slang

source/slang/slang-check-conformance.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,15 @@ namespace Slang
8282
// where `[...]` represents the "hole" we leave
8383
// open to fill in next.
8484
//
85+
DeclaredSubtypeWitness* declaredWitness = m_astBuilder->create<DeclaredSubtypeWitness>();
86+
declaredWitness->sub = bb->sub;
87+
declaredWitness->sup = bb->sup;
88+
declaredWitness->declRef = bb->declRef;
89+
8590
TransitiveSubtypeWitness* transitiveWitness = m_astBuilder->create<TransitiveSubtypeWitness>();
86-
transitiveWitness->sub = bb->sub;
91+
transitiveWitness->sub = subType;
8792
transitiveWitness->sup = bb->sup;
88-
transitiveWitness->midToSup = bb->declRef;
93+
transitiveWitness->midToSup = declaredWitness;
8994

9095
// Fill in the current hole, and then set the
9196
// hole to point into the node we just created.

source/slang/slang-check-expr.cpp

+34-12
Original file line numberDiff line numberDiff line change
@@ -321,21 +321,43 @@ namespace Slang
321321
bb = ConstructDerefExpr(bb, loc);
322322
break;
323323

324-
case LookupResultItem::Breadcrumb::Kind::Constraint:
324+
case LookupResultItem::Breadcrumb::Kind::SuperType:
325325
{
326-
// TODO: do we need to make something more
327-
// explicit here?
328-
auto expr = ConstructDeclRefExpr(
329-
breadcrumb->declRef,
330-
bb,
331-
loc);
332-
333-
if(bb && bb->type.isLeftValue)
326+
// Note: a lookup through a super-type can
327+
// occur even in the case of a `static` member,
328+
// so we only modify the base expression here
329+
// if there is one.
330+
//
331+
if( bb )
334332
{
335-
expr->type.isLeftValue = true;
336-
}
333+
// We know that the breadcrumb reprsents a
334+
// cast of the base expression to a super type,
335+
// so we construct that cast explicitly here.
336+
//
337+
auto witness = as<SubtypeWitness>(breadcrumb->val);
338+
SLANG_ASSERT(witness);
339+
auto expr = createCastToSuperTypeExpr(witness->sup, bb, witness);
340+
341+
// Note that we allow a cast of an l-value to
342+
// be used as an l-value here because it enables
343+
// `[mutating]` methods to be called, and
344+
// mutable properties to be modified, but this
345+
// is probably not *technically* correct, since
346+
// treating an l-value of type `Derived` as
347+
// an l-value of type `Base` implies that we
348+
// can assign an arbitrary value of type `Base`
349+
// to that l-value (which would be an error).
350+
//
351+
// TODO: make sure we believe there are no
352+
// issues here.
353+
//
354+
if(bb && bb->type.isLeftValue)
355+
{
356+
expr->type.isLeftValue = true;
357+
}
337358

338-
bb = expr;
359+
bb = expr;
360+
}
339361
}
340362
break;
341363

source/slang/slang-check-overload.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,7 @@ namespace Slang
10141014
ctorItem.breadcrumbs = new LookupResultItem::Breadcrumb(
10151015
LookupResultItem::Breadcrumb::Kind::Member,
10161016
typeItem.declRef,
1017+
nullptr,
10171018
typeItem.breadcrumbs);
10181019

10191020
OverloadCandidate candidate;

source/slang/slang-lookup.cpp

+80-35
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ struct BreadcrumbInfo
2222
LookupResultItem::Breadcrumb::Kind kind;
2323
LookupResultItem::Breadcrumb::ThisParameterMode thisParameterMode = LookupResultItem::Breadcrumb::ThisParameterMode::Default;
2424
DeclRef<Decl> declRef;
25+
Val* val = nullptr;
2526
BreadcrumbInfo* prev = nullptr;
2627
};
2728

@@ -165,6 +166,7 @@ LookupResultItem CreateLookupResultItem(
165166
breadcrumbs = new LookupResultItem::Breadcrumb(
166167
bb->kind,
167168
bb->declRef,
169+
bb->val,
168170
breadcrumbs,
169171
bb->thisParameterMode);
170172
}
@@ -267,33 +269,42 @@ LookupResult lookUpDirectAndTransparentMembers(
267269
return result;
268270
}
269271

270-
271272
static SubtypeWitness* _makeSubtypeWitness(
272273
ASTBuilder* astBuilder,
273274
Type* subType,
274275
SubtypeWitness* subToMidWitness,
275276
Type* superType,
276-
DeclRef<TypeConstraintDecl> midToSuperConstraint)
277+
SubtypeWitness* midtoSuperWitness)
277278
{
278279
if(subToMidWitness)
279280
{
280281
TransitiveSubtypeWitness* transitiveWitness = astBuilder->create<TransitiveSubtypeWitness>();
281282
transitiveWitness->subToMid = subToMidWitness;
282-
transitiveWitness->midToSup = midToSuperConstraint;
283+
transitiveWitness->midToSup = midtoSuperWitness;
283284
transitiveWitness->sub = subType;
284285
transitiveWitness->sup = superType;
285286
return transitiveWitness;
286287
}
287288
else
288289
{
289-
DeclaredSubtypeWitness* declaredWitness = astBuilder->create<DeclaredSubtypeWitness>();
290-
declaredWitness->declRef = midToSuperConstraint;
291-
declaredWitness->sub = subType;
292-
declaredWitness->sup = superType;
293-
return declaredWitness;
290+
return midtoSuperWitness;
294291
}
295292
}
296293

294+
static SubtypeWitness* _makeSubtypeWitness(
295+
ASTBuilder* astBuilder,
296+
Type* subType,
297+
SubtypeWitness* subToMidWitness,
298+
Type* superType,
299+
DeclRef<TypeConstraintDecl> midToSuperConstraint)
300+
{
301+
DeclaredSubtypeWitness* midToSuperWitness = astBuilder->create<DeclaredSubtypeWitness>();
302+
midToSuperWitness->declRef = midToSuperConstraint;
303+
midToSuperWitness->sub = subType;
304+
midToSuperWitness->sup = superType;
305+
return _makeSubtypeWitness(astBuilder, subType, subToMidWitness, superType, midToSuperWitness);
306+
}
307+
297308
// Same as the above, but we are specializing a type instead of a decl-ref
298309
static Type* _maybeSpecializeSuperType(
299310
ASTBuilder* astBuilder,
@@ -337,37 +348,16 @@ static void _lookUpMembersInSuperTypeImpl(
337348
LookupResult& ioResult,
338349
BreadcrumbInfo* inBreadcrumbs);
339350

340-
341351
static void _lookUpMembersInSuperType(
342-
ASTBuilder* astBuilder,
352+
ASTBuilder* astBuilder,
343353
Name* name,
344354
Type* leafType,
345-
SubtypeWitness* leafIsIntermediateWitness,
346-
DeclRef<TypeConstraintDecl> intermediateIsSuperConstraint,
355+
Type* superType,
356+
SubtypeWitness* leafIsSuperWitness,
347357
LookupRequest const& request,
348358
LookupResult& ioResult,
349359
BreadcrumbInfo* inBreadcrumbs)
350360
{
351-
if( request.semantics )
352-
{
353-
ensureDecl(request.semantics, intermediateIsSuperConstraint, DeclCheckState::CanUseBaseOfInheritanceDecl);
354-
}
355-
356-
// The super-type in the constraint (e.g., `Foo` in `T : Foo`)
357-
// will tell us a type we should use for lookup.
358-
//
359-
auto superType = getSup(astBuilder, intermediateIsSuperConstraint);
360-
//
361-
// We will go ahead and perform lookup using `superType`,
362-
// after dealing with some details.
363-
364-
auto leafIsSuperWitness = _makeSubtypeWitness(
365-
astBuilder,
366-
leafType,
367-
leafIsIntermediateWitness,
368-
superType,
369-
intermediateIsSuperConstraint);
370-
371361
// If we are looking up through an interface type, then
372362
// we need to be sure that we add an appropriate
373363
// "this type" substitution here, since that needs to
@@ -384,8 +374,8 @@ static void _lookUpMembersInSuperType(
384374
//
385375
BreadcrumbInfo breadcrumb;
386376
breadcrumb.prev = inBreadcrumbs;
387-
breadcrumb.kind = LookupResultItem::Breadcrumb::Kind::Constraint;
388-
breadcrumb.declRef = intermediateIsSuperConstraint;
377+
breadcrumb.kind = LookupResultItem::Breadcrumb::Kind::SuperType;
378+
breadcrumb.val = leafIsSuperWitness;
389379
breadcrumb.prev = inBreadcrumbs;
390380

391381
// TODO: Need to consider case where this might recurse infinitely (e.g.,
@@ -399,6 +389,39 @@ static void _lookUpMembersInSuperType(
399389
_lookUpMembersInSuperTypeImpl(astBuilder, name, leafType, superType, leafIsSuperWitness, request, ioResult, &breadcrumb);
400390
}
401391

392+
static void _lookUpMembersInSuperType(
393+
ASTBuilder* astBuilder,
394+
Name* name,
395+
Type* leafType,
396+
SubtypeWitness* leafIsIntermediateWitness,
397+
DeclRef<TypeConstraintDecl> intermediateIsSuperConstraint,
398+
LookupRequest const& request,
399+
LookupResult& ioResult,
400+
BreadcrumbInfo* inBreadcrumbs)
401+
{
402+
if( request.semantics )
403+
{
404+
ensureDecl(request.semantics, intermediateIsSuperConstraint, DeclCheckState::CanUseBaseOfInheritanceDecl);
405+
}
406+
407+
// The super-type in the constraint (e.g., `Foo` in `T : Foo`)
408+
// will tell us a type we should use for lookup.
409+
//
410+
auto superType = getSup(astBuilder, intermediateIsSuperConstraint);
411+
//
412+
// We will go ahead and perform lookup using `superType`,
413+
// after dealing with some details.
414+
415+
auto leafIsSuperWitness = _makeSubtypeWitness(
416+
astBuilder,
417+
leafType,
418+
leafIsIntermediateWitness,
419+
superType,
420+
intermediateIsSuperConstraint);
421+
422+
return _lookUpMembersInSuperType(astBuilder, name, leafType, superType, leafIsSuperWitness, request, ioResult, inBreadcrumbs);
423+
}
424+
402425
static void _lookUpMembersInSuperTypeDeclImpl(
403426
ASTBuilder* astBuilder,
404427
Name* name,
@@ -587,10 +610,32 @@ static void _lookUpMembersInSuperTypeImpl(
587610

588611
_lookUpMembersInSuperTypeDeclImpl(astBuilder, name, leafType, superType, leafIsSuperWitness, declRef, request, ioResult, inBreadcrumbs);
589612
}
590-
if (auto extractExistentialType = as<ExtractExistentialType>(superType))
613+
else if (auto extractExistentialType = as<ExtractExistentialType>(superType))
591614
{
592615
_lookUpMembersInSuperTypeDeclImpl(astBuilder, name, leafType, superType, leafIsSuperWitness, extractExistentialType->interfaceDeclRef, request, ioResult, inBreadcrumbs);
593616
}
617+
else if( auto thisType = as<ThisType>(superType) )
618+
{
619+
// We need to create a witness that represents the next link in the
620+
// chain. The `leafIsSuperWitness` represents the knowledge that `leafType : superType`
621+
// (and we know that `superType == thisType`, but we now need to extend that
622+
// with the knowledge that `thisType : thisType->interfaceTypeDeclRef`.
623+
//
624+
auto interfaceType = DeclRefType::create(astBuilder, thisType->interfaceDeclRef);
625+
626+
auto superIsInterfaceWitness = astBuilder->create<ThisTypeSubtypeWitness>();
627+
superIsInterfaceWitness->sub = superType;
628+
superIsInterfaceWitness->sup = interfaceType;
629+
630+
auto leafIsInterfaceWitness = _makeSubtypeWitness(
631+
astBuilder,
632+
leafType,
633+
leafIsSuperWitness,
634+
interfaceType,
635+
superIsInterfaceWitness);
636+
637+
_lookUpMembersInSuperTypeDeclImpl(astBuilder, name, leafType, interfaceType, leafIsInterfaceWitness, thisType->interfaceDeclRef, request, ioResult, inBreadcrumbs);
638+
}
594639
}
595640

596641
/// Perform lookup for `name` in the context of `type`.

0 commit comments

Comments
 (0)