diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2021-02-05 09:01:36 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-05 09:01:36 -0800 |
| commit | adb1131d08f28f0bc5f729e88b73cf22846c86c5 (patch) | |
| tree | 28139e39f16a7375baa42b41b0a523bfc87f667b /source | |
| parent | fb053433ef64bbae50a8a10ea4381a5695019fac (diff) | |
Initial implementation of interface conjunctions (#1691)
The basic feature here is the ability to use the `&` operator to produce the conjunction/intersection of two interfaces. That is, you can have interfaces:
interface IFirst { int getFirst(); }
interface ISecond { int getSecoond(); }
and if you need a generic function where the type parameter `T` must conform to *both* of these interfaces, you express that by constraining the parameter to the intersection of the interfaces:
void someFunction<T : IFirst & ISecond>(T value) { ... }
Without this feature, the main alternative an application would have is to define an intermediate interface, like:
interface IBoth : IFirst, ISecond {}
Forcing users to deal with an intermediate interface creates more work for type authors (they need to remember to inherit from the right combined interface(s)), or for `extension` authors (when you add `ISecond` to a type that used to just support `IFirst`, you had better also add `IBoth`). In the worst case, a family of N related "leaf" interfaces would give rise to an exponential number of intermediate interfaces to represnt the possible combinations.
A conjunction like `IFirst & ISecond` is officially its own type, and can be used to declare a type alias:
typealias IBoth = IFirst & ISecond;
This change only includes the first pass of work on this feature, so there are several caveats to be aware of:
* Using a conjunction as part of an inheritance clause is not yet supported (e.g., `struct X : IFirst & ISecond`). This is true even if the conjunction was introduced by an intermediate `typealias`
* The `&` syntax introduced here is only parsed in places where only a type (not an expression) is possible. This means you cannot do things like cast to a conjunction with `(IFirst & ISecond)(someValue)`.
* This work *should* apply to conjunctions of more than two interfaces (like `IA & IB & IC`) but that has not yet been tested
* In the long run it may be sensible to allow conjunctions that use concrete types, but we really ought to have the semantic checking logic rule that out for now.
* During testing, I encountered compiler crashes when trying to use this feature together with `property` declarations. Further investigation and debugging is called for.
* The handling of conjunction types is currently incomplete, in that there are many equivalences the compiler does not yet understand. For example, it is clear that `IA & IB` is equivalent to `IB & IA`, but the compiler currently does not understand this and will treat them as different types. A deeper implementation approach is called for.
* Conjunctions are currently only supported for generic type parameter constraints, when performing full specialization. Use of conjunctions for existential-type value parameters or with dynamic dispatch is not yet supported.
Diffstat (limited to 'source')
| -rw-r--r-- | source/core/slang-hash.h | 22 | ||||
| -rw-r--r-- | source/slang/slang-ast-builder.cpp | 8 | ||||
| -rw-r--r-- | source/slang/slang-ast-builder.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-ast-expr.h | 9 | ||||
| -rw-r--r-- | source/slang/slang-ast-type.cpp | 93 | ||||
| -rw-r--r-- | source/slang/slang-ast-type.h | 18 | ||||
| -rw-r--r-- | source/slang/slang-ast-val.h | 28 | ||||
| -rw-r--r-- | source/slang/slang-check-conformance.cpp | 18 | ||||
| -rw-r--r-- | source/slang/slang-check-constraint.cpp | 33 | ||||
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 27 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 6 | ||||
| -rw-r--r-- | source/slang/slang-ir-inst-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-ir-insts.h | 34 | ||||
| -rw-r--r-- | source/slang/slang-ir-lower-generic-function.cpp | 16 | ||||
| -rw-r--r-- | source/slang/slang-ir.cpp | 30 | ||||
| -rw-r--r-- | source/slang/slang-ir.h | 8 | ||||
| -rw-r--r-- | source/slang/slang-lookup.cpp | 57 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 212 | ||||
| -rw-r--r-- | source/slang/slang-parser.cpp | 60 | ||||
| -rw-r--r-- | source/slang/slang.natvis | 5 |
20 files changed, 659 insertions, 29 deletions
diff --git a/source/core/slang-hash.h b/source/core/slang-hash.h index 5a0766c98..b2a583744 100644 --- a/source/core/slang-hash.h +++ b/source/core/slang-hash.h @@ -174,18 +174,40 @@ namespace Slang public: Hasher() {} + /// Hash the given `value` and combine it into this hash state template<typename T> void hashValue(T const& value) { + // TODO: Eventually, we should replace `getHashCode` + // with a "hash into" operation that takes the value + // and a `Hasher`. + m_hashCode = combineHash(m_hashCode, getHashCode(value)); } + /// Hash the given `object` and combine it into this hash state template<typename T> void hashObject(T const& object) { + // TODO: Eventually, we should replace `getHashCode` + // with a "hash into" operation that takes the value + // and a `Hasher`. + m_hashCode = combineHash(m_hashCode, object->getHashCode()); } + /// Combine the given `hash` code into the hash state. + /// + /// Note: users should prefer to use `hashValue` or `hashObject` + /// when possible, as they may be able to ensure a higher-quality + /// hash result (e.g., by using more bits to represent the state + /// during hashing than are used for the final hash code). + /// + void addHash(HashCode hash) + { + m_hashCode = combineHash(m_hashCode, hash); + } + HashCode getResult() const { return m_hashCode; diff --git a/source/slang/slang-ast-builder.cpp b/source/slang/slang-ast-builder.cpp index 3b97bfbb7..240b39d6c 100644 --- a/source/slang/slang-ast-builder.cpp +++ b/source/slang/slang-ast-builder.cpp @@ -257,6 +257,14 @@ VectorExpressionType* ASTBuilder::getVectorType( return as<VectorExpressionType>(DeclRefType::create(this, declRef)); } +Type* ASTBuilder::getAndType(Type* left, Type* right) +{ + auto type = create<AndType>(); + type->left = left; + type->right = right; + return type; +} + TypeType* ASTBuilder::getTypeType(Type* type) { return create<TypeType>(type); diff --git a/source/slang/slang-ast-builder.h b/source/slang/slang-ast-builder.h index 08d22f6ca..702b02e0a 100644 --- a/source/slang/slang-ast-builder.h +++ b/source/slang/slang-ast-builder.h @@ -153,6 +153,8 @@ public: VectorExpressionType* getVectorType(Type* elementType, IntVal* elementCount); + Type* getAndType(Type* left, Type* right); + TypeType* getTypeType(Type* type); /// Helpers to get type info from the SharedASTBuilder diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h index e2e64e2a2..a5024460b 100644 --- a/source/slang/slang-ast-expr.h +++ b/source/slang/slang-ast-expr.h @@ -325,4 +325,13 @@ class ThisTypeExpr: public Expr RefPtr<Scope> scope; }; + /// A type expression of the form `Left & Right`. +class AndTypeExpr : public Expr +{ + SLANG_AST_CLASS(AndTypeExpr); + + TypeExp left; + TypeExp right; +}; + } // namespace Slang diff --git a/source/slang/slang-ast-type.cpp b/source/slang/slang-ast-type.cpp index 9c00c13ba..79df8e48b 100644 --- a/source/slang/slang-ast-type.cpp +++ b/source/slang/slang-ast-type.cpp @@ -938,5 +938,98 @@ Val* ThisType::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet s return substType; } +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! AndType !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +String AndType::_toStringOverride() +{ + String result; + result.append(left->toString()); + result.append(" & "); + result.append(right->toString()); + return result; +} + +bool AndType::_equalsImplOverride(Type * type) +{ + auto other = as<AndType>(type); + if (!other) + return false; + + if(!left->equals(other->left)) + return false; + if(!right->equals(other->right)) + return false; + + return true; +} + +HashCode AndType::_getHashCodeOverride() +{ + Hasher hasher; + hasher.hashObject(left); + hasher.hashObject(right); + return hasher.getResult(); +} + +Type* AndType::_createCanonicalTypeOverride() +{ + AndType* canType = m_astBuilder->create<AndType>(); + + // TODO: proper canonicalization of an `&` type relies on + // several different things: + // + // * We need to re-associate types that might involve + // nesting of `&`, such as `(A & B) & (C & D)`, into + // a canonical form where the nesting is consistent + // (i.e., always left- or right-associative). + // + // * We need to commute types so that they are in a + // consistent order, so that `A & B` and `B & A` both + // result in the same canonicalization. This requirement + // implies that we must invent a total order on types. + // + // * We need to canonicalize `&` types where one of the + // elements might be implied by another. E.g., if we + // have `interface IDerived : IBase`, then a type like + // `IDerived & IBase` is equivalent to just `IDerived` + // because the presence of an `IBase` conformance is + // implied. A special case of the above is the possibility + // of duplicates in the list of types (e.g., `A & B & A`). + // + // * The previous requirement raises the problem that + // the relationships between `interface`s might either + // evolve over time, or be subject to `extension` + // declarations in other modules. The canonicalization + // algorithm must be clear about what information it + // is allowed to make use of, as this can/will affect + // binary interfaces (via mangled names). + // + // We are going to completely ignore these issues for + // right now, in the name of getting something up and running. + // + canType->left = left->getCanonicalType(); + canType->right = right->getCanonicalType(); + + return canType; +} + +Val* AndType::_substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff) +{ + int diff = 0; + + auto substLeft = as<Type>(left ->substituteImpl(astBuilder, subst, &diff)); + auto substRight = as<Type>(right->substituteImpl(astBuilder, subst, &diff)); + + if(!diff) + return this; + + (*ioDiff)++; + + AndType* substType = m_astBuilder->create<AndType>(); + substType->left = substLeft; + substType->right = substRight; + return substType; +} + } // namespace Slang diff --git a/source/slang/slang-ast-type.h b/source/slang/slang-ast-type.h index 4c2242794..d4d626182 100644 --- a/source/slang/slang-ast-type.h +++ b/source/slang/slang-ast-type.h @@ -676,4 +676,22 @@ class ThisType : public Type Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); }; + /// The type of `A & B` where `A` and `B` are types + /// + /// A value `v` is of type `A & B` if it is both of type `A` and of type `B`. +class AndType : public Type +{ + SLANG_AST_CLASS(AndType) + + Type* left; + Type* right; + + // Overrides should be public so base classes can access + String _toStringOverride(); + bool _equalsImplOverride(Type* type); + HashCode _getHashCodeOverride(); + Type* _createCanonicalTypeOverride(); + Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); +}; + } // namespace Slang diff --git a/source/slang/slang-ast-val.h b/source/slang/slang-ast-val.h index af137395b..dc32b3faa 100644 --- a/source/slang/slang-ast-val.h +++ b/source/slang/slang-ast-val.h @@ -213,4 +213,32 @@ class DynamicSubtypeWitness : public SubtypeWitness SLANG_AST_CLASS(DynamicSubtypeWitness) }; + /// A witness that `T : L & R` because `T : L` and `T : R` +class ConjunctionSubtypeWitness : public SubtypeWitness +{ + SLANG_AST_CLASS(ConjunctionSubtypeWitness) + + /// Witness that `sub : sup->left` + Val* leftWitness; + + /// Witness that `sub : sup->right` + Val* rightWitness; +}; + + /// A witness that `T : X` because `T : X & Y` or `T : Y & X` +class ExtractFromConjunctionSubtypeWitness : public SubtypeWitness +{ + SLANG_AST_CLASS(ExtractFromConjunctionSubtypeWitness) + + /// Witness that `T : L & R` for some `R` + Val* conunctionWitness; + + /// The zero-based index of the super-type we care about in the conjunction + /// + /// If `conunctionWitness` is `T : X & Y` then this index should be zero if + /// we want to represent `T : X` and one if we want `T : Y`. + /// + int indexInConjunction; +}; + } // namespace Slang diff --git a/source/slang/slang-check-conformance.cpp b/source/slang/slang-check-conformance.cpp index 6a397e8b6..174d4be89 100644 --- a/source/slang/slang-check-conformance.cpp +++ b/source/slang/slang-check-conformance.cpp @@ -429,6 +429,24 @@ namespace Slang } } } + else if( auto andType = as<AndType>(sup) ) + { + // A type `T` is a subtype of `A & B` if `T` is a + // subtype of `A` and `T` is a subtype of `B`. + // + auto leftWitness = tryGetSubtypeWitness(sub, andType->left); + if(!leftWitness) return nullptr; + + auto rightWitness = tryGetSubtypeWitness(sub, andType->right); + if(!rightWitness) return nullptr; + + ConjunctionSubtypeWitness* w = m_astBuilder->create<ConjunctionSubtypeWitness>(); + w->leftWitness = leftWitness; + w->rightWitness = rightWitness; + w->sub = sub; + w->sup = sup; + return w; + } return nullptr; } diff --git a/source/slang/slang-check-constraint.cpp b/source/slang/slang-check-constraint.cpp index 4d1379016..a03043f31 100644 --- a/source/slang/slang-check-constraint.cpp +++ b/source/slang/slang-check-constraint.cpp @@ -659,6 +659,21 @@ namespace Slang return false; } + bool SemanticsVisitor::TryUnifyConjunctionType( + ConstraintSystem& constraints, + AndType* fst, + Type* snd) + { + // Unifying a type `T` with `A & B` amounts to unifying + // `T` with `A` and also `T` with `B`. + // + // If either unification is impossible, then the full + // case is also impossible. + // + return TryUnifyTypes(constraints, fst->left, snd) + && TryUnifyTypes(constraints, fst->right, snd); + } + bool SemanticsVisitor::TryUnifyTypes( ConstraintSystem& constraints, Type* fst, @@ -674,6 +689,24 @@ namespace Slang if (auto sndErrorType = as<ErrorType>(snd)) return true; + // If one or the other of the types is a conjunction `X & Y`, + // then we want to recurse on both `X` and `Y`. + // + // Note that we check this case *before* we check if one of + // the types is a generic parameter below, so that we should + // never end up trying to match up a type parameter with + // a conjunction directly, and will instead find all of the + // "leaf" types we need to constrain it to. + // + if( auto fstAndType = as<AndType>(fst) ) + { + return TryUnifyConjunctionType(constraints, fstAndType, snd); + } + if( auto sndAndType = as<AndType>(snd) ) + { + return TryUnifyConjunctionType(constraints, sndAndType, fst); + } + // A generic parameter type can unify with anything. // TODO: there actually needs to be some kind of "occurs check" sort // of thing here... diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 47f5436c5..736eee6fb 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -2112,4 +2112,31 @@ namespace Slang return CreateErrorExpr(expr); } + Expr* SemanticsExprVisitor::visitAndTypeExpr(AndTypeExpr* expr) + { + // The left and right sides of an `&` for types must both be types. + // + expr->left = CheckProperType(expr->left); + expr->right = CheckProperType(expr->right); + + // TODO: We should enforce some rules here about what is allowed + // for the `left` and `right` types. + // + // For now, the right rule is that they probably need to either + // be interfaces, or conjunctions thereof. + // + // Eventually it may be valuable to support more flexible + // types in conjunctions, especialy in cases where inheritance + // gets involved. + + // The result of this expression is an `AndType`, which we need + // to wrap in a `TypeType` to indicate that the result is the type + // itself and not a value of that type. + // + auto andType = m_astBuilder->getAndType(expr->left.type, expr->right.type); + expr->type = m_astBuilder->getTypeType(andType); + + return expr; + } + } diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index b3c5e46f9..f1aae6a10 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -1387,6 +1387,11 @@ namespace Slang Type* fst, Type* snd); + bool TryUnifyConjunctionType( + ConstraintSystem& constraints, + AndType* fst, + Type* snd); + // Is the candidate extension declaration actually applicable to the given type DeclRef<ExtensionDecl> ApplyExtensionToType( ExtensionDecl* extDecl, @@ -1573,6 +1578,7 @@ namespace Slang Expr* visitThisExpr(ThisExpr* expr); Expr* visitThisTypeExpr(ThisTypeExpr* expr); + Expr* visitAndTypeExpr(AndTypeExpr* expr); }; struct SemanticsStmtVisitor diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h index 2cab493bd..082de1c42 100644 --- a/source/slang/slang-ir-inst-defs.h +++ b/source/slang/slang-ir-inst-defs.h @@ -51,6 +51,8 @@ INST(Nop, nop, 0, 0) INST(TaggedUnionType, TaggedUnion, 0, 0) + INST(ConjunctionType, Conjunction, 0, 0) + /* BindExistentialsTypeBase */ // A `BindExistentials<B, T0,w0, T1,w1, ...>` represents diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index 3c7406d3b..bde7be24c 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -1850,6 +1850,11 @@ struct IRBuilder IRDynamicType* getDynamicType(); IRTupleType* getTupleType(UInt count, IRType* const* types); + IRTupleType* getTupleType(List<IRType*> const& types) + { + return getTupleType(types.getCount(), types.getBuffer()); + } + IRTupleType* getTupleType(IRType* type0, IRType* type1); IRTupleType* getTupleType(IRType* type0, IRType* type1, IRType* type2); IRTupleType* getTupleType(IRType* type0, IRType* type1, IRType* type2, IRType* type3); @@ -1940,6 +1945,18 @@ struct IRBuilder IRType* getPseudoPtrType( IRType* concreteType); + IRType* getConjunctionType( + UInt typeCount, + IRType* const* types); + + IRType* getConjunctionType( + IRType* type0, + IRType* type1) + { + IRType* types[] = { type0, type1 }; + return getConjunctionType(2, types); + } + // Set the data type of an instruction, while preserving // its rate, if any. void setDataType(IRInst* inst, IRType* dataType); @@ -2018,6 +2035,23 @@ struct IRBuilder IRInst* emitMakeRTTIObject(IRInst* typeInst); IRInst* emitMakeTuple(IRType* type, UInt count, IRInst* const* args); + IRInst* emitMakeTuple(UInt count, IRInst* const* args); + + IRInst* emitMakeTuple(IRType* type, List<IRInst*> const& args) + { + return emitMakeTuple(type, args.getCount(), args.getBuffer()); + } + + IRInst* emitMakeTuple(List<IRInst*> const& args) + { + return emitMakeTuple(args.getCount(), args.getBuffer()); + } + + IRInst* emitMakeTuple(IRInst* arg0, IRInst* arg1) + { + IRInst* args[] = { arg0, arg1 }; + return emitMakeTuple(SLANG_COUNT_OF(args), args); + } IRInst* emitGetTupleElement(IRType* type, IRInst* tuple, UInt element); diff --git a/source/slang/slang-ir-lower-generic-function.cpp b/source/slang/slang-ir-lower-generic-function.cpp index 11e1c0f04..185655905 100644 --- a/source/slang/slang-ir-lower-generic-function.cpp +++ b/source/slang/slang-ir-lower-generic-function.cpp @@ -159,22 +159,30 @@ namespace Slang { if (auto entry = as<IRInterfaceRequirementEntry>(interfaceType->getOperand(i))) { + // Note: The logic that creates the `IRInterfaceRequirementEntry`s does + // not currently guarantee that the *value* part of each key-value pair + // gets filled in. We thus need to defend against a null `requirementVal` + // here, at least until the underlying issue gets resolved. + // + IRInst* requirementVal = entry->getRequirementVal(); IRInst* loweredVal = nullptr; - if (auto funcType = as<IRFuncType>(entry->getRequirementVal())) + if(!requirementVal) + {} + else if (auto funcType = as<IRFuncType>(requirementVal)) { loweredVal = lowerFuncType(&builder, funcType, Dictionary<IRInst*, IRInst*>(), ArrayView<IRInst*>()); } - else if (auto genericFuncType = as<IRGeneric>(entry->getRequirementVal())) + else if (auto genericFuncType = as<IRGeneric>(requirementVal)) { loweredVal = lowerGenericFuncType(&builder, genericFuncType); } - else if (entry->getRequirementVal()->op == kIROp_AssociatedType) + else if (requirementVal->op == kIROp_AssociatedType) { loweredVal = builder.getRTTIHandleType(); } else { - loweredVal = entry->getRequirementVal(); + loweredVal = requirementVal; } auto newEntry = builder.createInterfaceRequirementEntry(entry->getRequirementKey(), loweredVal); newEntries.add(newEntry); diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index 7e84ca66a..313e48502 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -2774,6 +2774,14 @@ namespace Slang return getType(kIROp_PseudoPtrType, SLANG_COUNT_OF(operands), operands); } + IRType* IRBuilder::getConjunctionType( + UInt typeCount, + IRType* const* types) + { + return getType(kIROp_ConjunctionType, typeCount, (IRInst* const*)types); + } + + void IRBuilder::setDataType(IRInst* inst, IRType* dataType) { @@ -3043,8 +3051,30 @@ namespace Slang return emitIntrinsicInst(type, kIROp_MakeTuple, count, args); } + IRInst* IRBuilder::emitMakeTuple(UInt count, IRInst* const* args) + { + List<IRType*> types; + for(UInt i = 0; i < count; ++i) + types.add(args[i]->getFullType()); + + auto type = getTupleType(types); + return emitMakeTuple(type, count, args); + } + IRInst* IRBuilder::emitGetTupleElement(IRType* type, IRInst* tuple, UInt element) { + // As a quick simplification/optimization, if the user requests + // `getTupleElement(makeTuple(a_0, a_1, ... a_N), i)` then we should + // just return `a_i`, provided that the index is properly in range. + // + if( auto makeTuple = as<IRMakeTuple>(tuple) ) + { + if( element < makeTuple->getOperandCount() ) + { + return makeTuple->getOperand(element); + } + } + IRInst* args[] = { tuple, getIntValue(getIntType(), element) }; return emitIntrinsicInst(type, kIROp_GetTupleElement, 2, args); } diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h index c41fa9708..787f06fa0 100644 --- a/source/slang/slang-ir.h +++ b/source/slang/slang-ir.h @@ -1252,6 +1252,14 @@ struct IRTaggedUnionType : IRType IR_LEAF_ISA(TaggedUnionType) }; +struct IRConjunctionType : IRType +{ + IR_LEAF_ISA(ConjunctionType) + + Int getCaseCount() { return getOperandCount(); } + IRType* getCaseType(Int index) { return (IRType*) getOperand(index); } +}; + /// Represents a tuple. Tuples are created by `IRMakeTuple` and its elements /// are accessed via `GetTupleElement(tupleValue, IRIntLit)`. struct IRTupleType : IRType diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp index b54b09d63..c50364201 100644 --- a/source/slang/slang-lookup.cpp +++ b/source/slang/slang-lookup.cpp @@ -636,6 +636,63 @@ static void _lookUpMembersInSuperTypeImpl( _lookUpMembersInSuperType(astBuilder, name, leafType, interfaceType, leafIsInterfaceWitness, request, ioResult, inBreadcrumbs); } + else if( auto andType = as<AndType>(superType) ) + { + // We have a type of the form `leftType & rightType` and we need to perform + // lookup in both `leftType` and `rightType`. + // + auto leftType = andType->left; + auto rightType = andType->right; + + // Operationally, we are in a situation where we have a witness + // that the `leafType` we are doing lookup on is an subtype + // of `superType` (which is `leftType & rightType`) and now we need + // to construct a witness that `leafType` is a subtype of + // the `Left` type. + // + // Effectively, we have a witness that `T : X & Y` and we + // need to extract from it a witness that `T : X`. + // Fortunately, we have a class of subtype witness that does + // *precisely* this: + // + auto leafIsLeftWitness = astBuilder->create<ExtractFromConjunctionSubtypeWitness>(); + // + // Our witness will be to the fact that `leafType` is a subtype of `leftType` + // + leafIsLeftWitness->sub = leafType; + leafIsLeftWitness->sup = leftType; + // + // The evidence for the subtype relationship will be a witness + // proving that `leafType : leftType & rightType`: + // + leafIsLeftWitness->conunctionWitness = leafIsSuperWitness; + // + // ... along with the index of the desired super-type in + // that conjunction. The index of `leftType` in `leftType & rightType` + // is zero. + // + leafIsLeftWitness->indexInConjunction = 0; + + // The witness for the fact that `leafType : rightType` is the + // same as for the left case, just with a different index into + // the conjunction. + // + auto leafIsRightWitness = astBuilder->create<ExtractFromConjunctionSubtypeWitness>(); + leafIsRightWitness->conunctionWitness = leafIsSuperWitness; + leafIsRightWitness->indexInConjunction = 1; + leafIsRightWitness->sub = leafType; + leafIsRightWitness->sup = rightType; + + // We then perform lookup on both sides of the conjunction, and + // accumulate whatever items are found on either/both sides. + // + // For each recursive lookup, we pass the appropriate pair of + // the type to look up in and the witness of the subtype + // relationship. + // + _lookUpMembersInSuperType(astBuilder, name, leafType, leftType, leafIsLeftWitness, request, ioResult, inBreadcrumbs); + _lookUpMembersInSuperType(astBuilder, name, leafType, rightType, leafIsRightWitness, request, ioResult, inBreadcrumbs); + } } /// Perform lookup for `name` in the context of `type`. diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index f1a7c477b..159b2478c 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -1438,6 +1438,61 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower return LoweredValInfo::simple(context->thisTypeWitness); } + LoweredValInfo visitConjunctionSubtypeWitness(ConjunctionSubtypeWitness* val) + { + // A witness `T : L & R` for a conformance of `T` to a conjunction of + // types `L` and `R` will be lowered as a tuple of two witnesses: one + // for `T : L` and one for `T : R`. Luckily, those two conformances + // are exactly what the `ConjunctionSubtypeWitness` stores, so we just + // need to lower them individually and make a tuple. + // + auto left = lowerSimpleVal(context, val->leftWitness); + auto right = lowerSimpleVal(context, val->rightWitness); + return LoweredValInfo::simple(getBuilder()->emitMakeTuple(left, right)); + } + + LoweredValInfo visitExtractFromConjunctionSubtypeWitness(ExtractFromConjunctionSubtypeWitness* val) + { + auto builder = getBuilder(); + + // We know from `visitConjunctionSubtypeWitness` that a witness for a relationship + // like `T : L & R` will be a tuple `(w_l, w_r)` where `w_l` is a witness + // for `T : L` and `w_r` will be a witness for `T : R`. + // + // An `ExtractFromConjunctionSubtypeWitness` represents the intention to + // extract one of those two sub-witnesses. It directly stores the original + // witness that `T : L & R`, so lower that first and expect it to be + // a value of tuple type. + // + auto conjunctionWitness = lowerSimpleVal(context, val->conunctionWitness); + auto conjunctionTupleType = as<IRTupleType>(conjunctionWitness->getDataType()); + SLANG_ASSERT(conjunctionTupleType); + + // The `ExtractFromConjunctionSubtypeWitness` also stores the index of + // the witness/supertype we want in the conjunction `L & R`. + // + auto indexInConjunction = val->indexInConjunction; + + // We want to extract the appropriate element from the tuple based on + // the index, but to know the type of the result we need to look up + // the element type that corresponds to that index. + // + // TODO: `IRTupleType` should really have `getElementCount()` and + // `getElementType(index)` accessors. + // + auto elementType = (IRType*) conjunctionTupleType->getOperand(indexInConjunction); + + // With the information we've extracted above, we now just need to + // extract the appropriate element from the `(w_l, w_r)` tuple of + // witnesses, and we will have our desired result. + // + return LoweredValInfo::simple(builder->emitGetTupleElement( + elementType, + conjunctionWitness, + indexInConjunction)); + } + + LoweredValInfo visitConstantIntVal(ConstantIntVal* val) { // TODO: it is a bit messy here that the `ConstantIntVal` representation @@ -1714,6 +1769,15 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower return emitDeclRef(context, type->interfaceDeclRef, getBuilder()->getTypeKind()); } + LoweredValInfo visitAndType(AndType* type) + { + auto left = lowerType(context, type->left); + auto right = lowerType(context, type->right); + + auto irType = getBuilder()->getConjunctionType(left, right); + return LoweredValInfo::simple(irType); + } + // We do not expect to encounter the following types in ASTs that have // passed front-end semantic checking. #define UNEXPECTED_CASE(NAME) IRType* visit##NAME(NAME*) { SLANG_UNEXPECTED(#NAME); UNREACHABLE_RETURN(nullptr); } @@ -3234,6 +3298,18 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> continue; } } + else if( auto andType = as<AndType>(e->type) ) + { + // TODO: We might eventually need to tell the difference + // between conjunctions of interfaces and conjunctions + // that might include non-interface types. + // + // For now we assume that any case to a conjunction + // is effectively a cast to an interface type. + // + e = castExpr->valueArg; + continue; + } break; } return e; @@ -3536,6 +3612,12 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> UNREACHABLE_RETURN(LoweredValInfo()); } + LoweredValInfo visitAndTypeExpr(AndTypeExpr* /*expr*/) + { + SLANG_UNIMPLEMENTED_X("'&' type expression during code generation"); + UNREACHABLE_RETURN(LoweredValInfo()); + } + LoweredValInfo visitAssocTypeDecl(AssocTypeDecl* decl) { SLANG_UNIMPLEMENTED_X("associatedtype expression during code generation"); @@ -6228,6 +6310,79 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> return Slang::maybeGetConstExprType(getBuilder(), type, decl); } + /// Emit appropriate generic parameters for a constraint, and return the value of that constraint. + /// + /// The `supType` paramete represents the super-type that a parameter is constrained to. + IRInst* emitGenericConstraintValue( + IRGenContext* subContext, + GenericTypeConstraintDecl* constraintDecl, + IRType* supType) + { + auto subBuilder = subContext->irBuilder; + + // There are two cases we care about here. + // + if(auto andType = as<IRConjunctionType>(supType)) + { + // The non-trivial case is when the constraint on a generic parameter + // was of the form `T : A & B`. In this case, we really want to + // emit the function with parameters for each of the two independent + // constraints `T : A` and `T : B`. + // + // We will loop over the "cases" of the conjunction (since + // the `IRConunctionType` can support more than just binary + // conjunctions) and recursively add constraints for each. + // + List<IRInst*> caseVals; + auto caseCount = andType->getCaseCount(); + for(Int i = 0; i < caseCount; ++i) + { + auto caseType = andType->getCaseType(i); + auto caseVal = emitGenericConstraintValue(subContext, constraintDecl, caseType); + caseVals.add(caseVal); + } + + return subBuilder->emitMakeTuple(caseVals); + } + else + { + // The case case is any other type being used as the constraint. + // + // The constraint will then map to a single generic parameter passing + // a witness table for conformance to the given `supType`. + // + auto param = subBuilder->emitParam(subBuilder->getWitnessTableType(supType)); + addNameHint(context, param, constraintDecl); + + // In order to support some of the "any-value" work in dynamic dispatch + // we have to attach the interface that was used as a constraint onto the + // type that is being constrained (which we expect to be a generic type + // parameter). + // + // TODO: It feels a bit gross to be doing this here; perhaps the front-end + // should handle propgation of value-size information from constraints + // back to generic parameters? + // + if (auto declRefType = as<DeclRefType>(constraintDecl->sub.type)) + { + auto typeParamDeclVal = subContext->findLoweredDecl(declRefType->declRef.decl); + SLANG_ASSERT(typeParamDeclVal && typeParamDeclVal->val); + subBuilder->addTypeConstraintDecoration(typeParamDeclVal->val, supType); + } + + return param; + } + } + + void emitGenericConstraintDecl( + IRGenContext* subContext, + GenericTypeConstraintDecl* constraintDecl) + { + auto supType = lowerType(context, constraintDecl->sup.type); + auto value = emitGenericConstraintValue(subContext, constraintDecl, supType); + setValue(subContext, constraintDecl, LoweredValInfo::simple(value)); + } + IRGeneric* emitOuterGeneric( IRGenContext* subContext, GenericDecl* genericDecl, @@ -6274,22 +6429,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> { if (auto constraintDecl = as<GenericTypeConstraintDecl>(member)) { - // TODO: use a `WitnessTableKind` to represent the - // classifier of the parameter. - auto supType = lowerType(context, constraintDecl->sup.type); - auto param = subBuilder->emitParam(subBuilder->getWitnessTableType(supType)); - addNameHint(context, param, constraintDecl); - setValue(subContext, constraintDecl, LoweredValInfo::simple(param)); - - // Attach the constraint interface type as a decoration to the IRParam value - // representing the generic parameter, to provide downstream passes knowledge - // of the correspondence. - if (auto declRefType = as<DeclRefType>(constraintDecl->sub.type)) - { - auto typeParamDeclVal = subContext->findLoweredDecl(declRefType->declRef.decl); - SLANG_ASSERT(typeParamDeclVal && typeParamDeclVal->val); - subBuilder->addTypeConstraintDecoration(typeParamDeclVal->val, supType); - } + emitGenericConstraintDecl(subContext, constraintDecl); } } @@ -7230,6 +7370,32 @@ static bool isInterfaceRequirement(Decl* decl) return false; } + /// Add flattened "leaf" elements from `val` to the `ioArgs` list +static void _addFlattenedTupleArgs( + List<IRInst*>& ioArgs, + IRInst* val) +{ + if( auto tupleVal = as<IRMakeTuple>(val) ) + { + // If the value is a tuple, we can add its element directly. + auto elementCount = tupleVal->getOperandCount(); + for( UInt i = 0; i < elementCount; ++i ) + { + _addFlattenedTupleArgs(ioArgs, tupleVal->getOperand(i)); + } + } + // + // TODO: We should handle the case here where `val` + // is not a `makeTuple` instruction, but still has + // a tuple *type*. In that case we should apply `getTupleElement` + // for each of its elements and then recurse on them. + // + else + { + ioArgs.add(val); + } +} + LoweredValInfo emitDeclRef( IRGenContext* context, Decl* decl, @@ -7293,7 +7459,19 @@ LoweredValInfo emitDeclRef( { auto irArgVal = lowerSimpleVal(context, argVal); SLANG_ASSERT(irArgVal); - irArgs.add(irArgVal); + + // It is possible that some of the arguments to the generic + // represent conformances to conjunction types like `A & B`. + // These conjunction conformances will appear as tuples in + // the IR, and we want to "flatten" them here so that we + // pass each "leaf" witness table as its own argument (to + // match the way that generic parameters are being emitted + // to the IR). + // + // TODO: This isn't a robust strategy if we ever have to deal + // with tuples as ordinary values. + // + _addFlattenedTupleArgs(irArgs, irArgVal); } // Once we have both the generic and its arguments, diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index 12fa90480..b15d215da 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -4066,21 +4066,67 @@ namespace Slang return parameter; } - Expr* Parser::ParseType() + /// Parse an "atomic" type expression. + /// + /// An atomic type expression is a type specifier followed by an optional + /// body in the case of a `struct`, `enum`, etc. + /// + static Expr* _parseAtomicTypeExpr(Parser* parser) { - auto typeSpec = parseTypeSpec(this); + auto typeSpec = parseTypeSpec(parser); if( typeSpec.decl ) { - AddMember(currentScope, typeSpec.decl); + AddMember(parser->currentScope, typeSpec.decl); } - auto typeExpr = typeSpec.expr; - - typeExpr = parsePostfixTypeSuffix(this, typeExpr); + return typeSpec.expr; + } - return typeExpr; + /// Parse a postfix type expression. + /// + /// A postfix type expression is an atomic type expression followed + /// by zero or more postifx suffixes like array brackets. + /// + static Expr* _parsePostfixTypeExpr(Parser* parser) + { + auto typeExpr = _parseAtomicTypeExpr(parser); + return parsePostfixTypeSuffix(parser, typeExpr); } + /// Parse an infix type expression. + /// + /// Currently, the only infix type expression we support is the `&` + /// operator for forming interface conjunctions. + /// + static Expr* _parseInfixTypeExpr(Parser* parser) + { + auto leftExpr = _parsePostfixTypeExpr(parser); + + for(;;) + { + // As long as the next token is an `&`, we will try + // to gobble up another type expression and form + // a conjunction type expression. + + auto loc = peekToken(parser).loc; + if(!AdvanceIf(parser, TokenType::OpBitAnd)) + break; + + auto rightExpr = _parsePostfixTypeExpr(parser); + + auto andExpr = parser->astBuilder->create<AndTypeExpr>(); + andExpr->loc = loc; + andExpr->left = TypeExp(leftExpr); + andExpr->right = TypeExp(rightExpr); + leftExpr = andExpr; + } + + return leftExpr; + } + Expr* Parser::ParseType() + { + return _parseInfixTypeExpr(this); + } TypeExp Parser::ParseTypeExp() { diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis index bf4e325e3..63ee313da 100644 --- a/source/slang/slang.natvis +++ b/source/slang/slang.natvis @@ -115,6 +115,8 @@ <Variable Name="pOperandInst" InitialValue="(IRInst*)nullptr"/> <Loop Condition="index < operandCount"> <Exec>pOperandInst = ((IRUse*)(&(typeUse) + 1 + index))->usedValue </Exec> + <Item Condition="pOperandInst == 0" Name="[operand{index}]">pOperandInst</Item> + <If Condition="pOperandInst != 0"> <Exec>child = pOperandInst->m_decorationsAndChildren.first</Exec> <Exec>nameDecoration = 0</Exec> <Loop Condition="child != 0"> @@ -132,7 +134,8 @@ </Loop> <Item Condition="nameDecoration != 0" Name="[operand{index}] : {((Slang::IRStringLit*)(((Slang::IRUse*)(nameDecoration + 1))->usedValue))->value.stringVal.chars,[((Slang::IRStringLit*)(((Slang::IRUse*)(nameDecoration + 1))->usedValue))->value.stringVal.numChars]s8}">*pOperandInst</Item> <Item Condition="nameDecoration == 0" Name="[operand{index}]">*pOperandInst</Item> - <Exec>index = index + 1</Exec> + </If> + <Exec>index = index + 1</Exec> </Loop> </CustomListItems> <Synthetic Name="[decorations/children]"> |
