diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2020-08-13 17:56:20 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-08-13 17:56:20 -0700 |
| commit | 2bfe62afb381f23dcbd39dfd6ba7448050272861 (patch) | |
| tree | 61a52e8ee173450ba30c35cecd6ac635b2544f5b | |
| parent | 482fd16b687c9e748119803752849ea2ffef4400 (diff) | |
Support property declarations in interfaces (#1494)
There are two main features in this change. First, we allow for `interface`s to declare `property` requirements, which can be satisfied by matching `property` declarations in a type that conforms to the interface:
interface IRectangle
{
property float width { get; }
property float height { get; }
}
struct Square : IRectangle
{
float size;
property float width { get { return size; } }
property float height { get { return size; } }
}
Second, we allow a type to satisfy a `property` requirement with an ordinary field of the same name:
struct Rectangle : IRectangle
{
float width;
float height;
// no explicit `property` declarations needed
}
The implementation of these features is mostly in `slang-check-decl.cpp` in the logic for checking conformance of a type to an interface.
The first feature simply requires adding logic to checking whether a candidate satisfying `property` declaration matches a required `property` declaration. To do so, it must have the same type, and an accessor to satisfy each of the required accessors.
The second feature requires adding logic to synthesize an AST `property` declaration for a type, based on a required `property` declaration and its accessors. This means that, more or less, any type where `this.name` yields a storage location that does what is needed can satisfy a property requirement (there is no specific rule that says the storage needs to be a field, although that is the most likely case).
The way that witnesses are stored for property declarations probably merits some description. During IR lowering, an abstract storage declaration like a subscript or `property` more or less desugars away, so that the actual interface requirements correspond to the accessors within it (the `get`, `set`, etc.). This means that a witness table should have entries/keys corresponding to the accessors and not the property itself. The process of finding/recording witnesses for `property` requirements thus installs entries for the individual accessors (with care taken to only install accessor witnesses once we are sure we have witnesses for all the requirements). Currently, the code also installs an entry for the property itself, although that is not strictly required, and might not be something we continue to do long-term.
(Aside: it was somewhat surprising that an end-to-end test of `property` declarations in `interface`s Just Worked without any changes to IR lowering.)
As we continue to write more code that synthesizes and checks AST expressions/statements, it becomes necessary to refactor the semantic checking logic so that it splits the recursive part (e.g., checking the operands of an assignment) from the validation part (e.g., checking that the assignment itself is valid). It is probably too big of a change to justify at this point, but it might be valuable in the future to have distinct hierarchies that represent unchecked and checked ASTs, with semantic checking mostly being a transformation from one to the other. The benefit of such a change is we could factor out a distinct "builder" API for constructing validated/checked AST nodes, with both semantic checking and AST synthesis being clients of that API.
| -rw-r--r-- | source/slang/slang-ast-support-types.h | 12 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 475 | ||||
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 35 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 26 | ||||
| -rw-r--r-- | tests/language-feature/properties/property-in-interface.slang | 47 | ||||
| -rw-r--r-- | tests/language-feature/properties/property-in-interface.slang.expected.txt | 4 |
6 files changed, 581 insertions, 18 deletions
diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h index 076c6cbbb..b8923643c 100644 --- a/source/slang/slang-ast-support-types.h +++ b/source/slang/slang-ast-support-types.h @@ -577,6 +577,18 @@ namespace Slang { return isSubClassOf(SyntaxClass<U>::getClass()); } + + template<typename U> + bool operator==(const SyntaxClass<U> other) const + { + return classInfo == other.classInfo; + } + + template<typename U> + bool operator!=(const SyntaxClass<U> other) const + { + return classInfo != other.classInfo; + } }; template<typename T> diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 3cc0fd0f5..9c8f022ae 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -1275,6 +1275,114 @@ namespace Slang return true; } + bool SemanticsVisitor::doesAccessorMatchRequirement( + DeclRef<AccessorDecl> satisfyingMemberDeclRef, + DeclRef<AccessorDecl> requiredMemberDeclRef) + { + // We require the AST node class of the satisfying accessor + // to be a subclass of the one from the required accessor. + // + // For our current accessor types, this amounts to requiring + // an exact match, but using a subtype test means that if + // we ever add an `ExtraSpecialGetDecl` that is a subclass + // of `GetDecl`, then one of those would be able to satisfy + // a `get` requirement. + // + auto satisfyingMemberClass = satisfyingMemberDeclRef.getDecl()->getClass(); + auto requiredMemberClass = requiredMemberDeclRef.getDecl()->getClass(); + if(!satisfyingMemberClass.isSubClassOfImpl(requiredMemberClass)) + return false; + + // We do not check the parameters or return types of accessors + // here, under the assumption that the validity checks for + // the parent `property` declaration would already make sure + // they are in order. + + // TODO: There are other checks we need to make here, like not letting + // an ordinary `set` satisfy a `[nonmutating] set` requirement. + + return true; + } + + bool SemanticsVisitor::doesPropertyMatchRequirement( + DeclRef<PropertyDecl> satisfyingMemberDeclRef, + DeclRef<PropertyDecl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable) + { + // The type of the satisfying member must match the type of the required member. + // + // Note: It is possible that a `get`-only property could be satisfied by + // a declaration that uses a subtype of the requirement, but that would not + // count as an "exact match" and we would rely on the logic to synthesize + // a stub implementation in that case. + // + auto satisfyingType = getType(getASTBuilder(), satisfyingMemberDeclRef); + auto requiredType = getType(getASTBuilder(), requiredMemberDeclRef); + if(!satisfyingType->equals(requiredType)) + return false; + + // Each accessor in the requirement must be accounted for by an accessor + // in the satisfying member. + // + // Note: it is fine for the satisfying member to provide *more* accessors + // than the original declaration. + // + Dictionary<DeclRef<AccessorDecl>, DeclRef<AccessorDecl>> mapRequiredToSatisfyingAccessorDeclRef; + for( auto requiredAccessorDeclRef : getMembersOfType<AccessorDecl>(requiredMemberDeclRef) ) + { + // We need to search for an accessor that can satisfy the requirement. + // + // For now we will do the simplest (and slowest) thing of a linear search, + // which is mostly fine because the number of accessors is bounded. + // + bool found = false; + for( auto satisfyingAccessorDeclRef : getMembersOfType<AccessorDecl>(satisfyingMemberDeclRef) ) + { + if( doesAccessorMatchRequirement(satisfyingAccessorDeclRef, requiredAccessorDeclRef) ) + { + // When we find a match on an accessor, we record it so that + // we can set up the witness values later, but we do *not* + // record it into the actual witness table yet, in case + // a later accessor comes along that doesn't find a match. + // + mapRequiredToSatisfyingAccessorDeclRef.Add(requiredAccessorDeclRef, satisfyingAccessorDeclRef); + found = true; + break; + } + } + if(!found) + return false; + } + + // Once things are done, we will install the satisfying values + // into the witness table for the requirements. + // + for( auto p : mapRequiredToSatisfyingAccessorDeclRef ) + { + witnessTable->requirementDictionary.Add( + p.Key, + RequirementWitness(p.Value)); + } + // + // Note: the property declaration itself isn't something that + // has a useful value/representation in downstream passes, so + // we are mostly just installing it into the witness table + // as a way to mark this requirement as being satisfied. + // + // TODO: It is possible that having a witness table entry that + // doesn't actually map to any IR value could create a problem + // in downstream passes. If such propblems arise, we should + // probably create a new `RequirementWitness` case that + // represents a witness value that is only needed by the front-end, + // and that can be ignored by IR and emit logic. + // + witnessTable->requirementDictionary.Add( + requiredMemberDeclRef.getDecl(), + RequirementWitness(satisfyingMemberDeclRef)); + return true; + } + + bool SemanticsVisitor::doesGenericSignatureMatchRequirement( DeclRef<GenericDecl> genDecl, DeclRef<GenericDecl> requirementGenDecl, @@ -1477,6 +1585,14 @@ namespace Slang return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable); } } + else if( auto propertyDeclRef = memberDeclRef.as<PropertyDecl>() ) + { + if( auto requiredPropertyDeclRef = requiredMemberDeclRef.as<PropertyDecl>() ) + { + ensureDecl(propertyDeclRef, DeclCheckState::CanUseFuncSignature); + return doesPropertyMatchRequirement(propertyDeclRef, requiredPropertyDeclRef, witnessTable); + } + } // Default: just assume that thing aren't being satisfied. return false; } @@ -1758,6 +1874,348 @@ namespace Slang return true; } + bool SemanticsVisitor::trySynthesizePropertyRequirementWitness( + ConformanceCheckingContext* context, + LookupResult const& lookupResult, + DeclRef<PropertyDecl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable) + { + // The situation here is that the context of an inheritance + // declaration didn't provide an exact match for a required + // property. E.g.: + // + // interface ICell { property value : int { get; set; } } + // struct MyCell : ICell + // { + // int value; + // } + // + // It is clear in this case that the `MyCell` type *can* + // satisfy the signature required by `ICell`, but it has + // no explicit `property` declaration, and instead just + // a field with the right name and type. + // + // The approach in this function will be to construct a + // synthesized `preoperty` along the lines of: + // + // struct MyCounter ... + // { + // ... + // property value_synthesized : int + // { + // get { return this.value; } + // set(newValue) { this.value = newValue; } + // } + // } + // + // That is, we construct a `property` with the correct type + // and with an accessor for each requirement, where the accesors + // all try to read or write `this.value`. + // + // If those synthesized accessors all type-check, then we can + // say that the type must satisfy the requirement structurally, + // even if there isn't an exact signature match. More + // importantly, the `property` we just synthesized can be + // used as a witness to the fact that the requirement is + // satisfied. + // + // The big-picture flow of the logic here is similar to + // `trySynthesizeMethodRequirementWitness()` above, and we + // will not comment this code as exhaustively, under the + // assumption that readers of the code don't benefit from + // having the exact same information stated twice. + + // With the introduction out of the way, let's get started + // constructing a synthesized `PropertyDecl`. + // + auto synPropertyDecl = m_astBuilder->create<PropertyDecl>(); + + // For now our synthesized property will use the name and source + // location of the requirement we are trying to satisfy. + // + // TODO: as it stands right now our syntesized property and its + // accesors will get mangled names, which we don't actually want. + // Leaving out the name here doesn't help matters, becaues then + // *all* synthesized members on a given type would share the same + // mangled name. + // + synPropertyDecl->nameAndLoc = requiredMemberDeclRef.getDecl()->nameAndLoc; + + // The type of our synthesized property will be the expected type + // of the interface requirement. + // + // TODO: This logic can/will run into problems if the type is, + // or uses, an associated type or `This`. + // + // Ideally we should be looking up the type using a `DeclRef` that + // refers to the interface requirement using a `ThisTypeSubstitution` + // that refers to the satisfying type declaration, and requirement + // checking for non-associated-type requirements should be done *after* + // requirement checking for associated-type requirements. + // + auto propertyType = getType(m_astBuilder, requiredMemberDeclRef); + synPropertyDecl->type.type = propertyType; + + // Our synthesized property will have an accessor declaration for + // each accessor of the requirement. + // + // TODO: If we ever start to support synthesis for subscript requirements, + // then we probably want to factor the accessor-related logic into + // a subroutine so that it can be shared between properties and subscripts. + // + Dictionary<DeclRef<AccessorDecl>, AccessorDecl*> mapRequiredAccessorToSynAccessor; + for( auto requiredAccessorDeclRef : getMembersOfType<AccessorDecl>(requiredMemberDeclRef) ) + { + // The synthesized accessor will be an AST node of the same class as + // the required accessor. + // + auto synAccessorDecl = (AccessorDecl*) m_astBuilder->createByNodeType(requiredAccessorDeclRef.getDecl()->astNodeType); + + // Whatever the required accessor returns, that is what our synthesized accessor will return. + // + synAccessorDecl->returnType.type = getResultType(m_astBuilder, requiredAccessorDeclRef); + + // Similarly, our synthesized accessor will have parameters matching those of the requirement. + // + // Note: in practice we expect that only `set` accessors will have any parameters, + // and they will only have a single parameter. + // + List<Expr*> synArgs; + for( auto requiredParamDeclRef : getParameters(requiredAccessorDeclRef) ) + { + auto paramType = getType(m_astBuilder, requiredParamDeclRef); + + // The synthesized parameter will ahve the same name and + // type as the parameter of the requirement. + // + auto synParamDecl = m_astBuilder->create<ParamDecl>(); + synParamDecl->nameAndLoc = requiredParamDeclRef.getDecl()->nameAndLoc; + synParamDecl->type.type = paramType; + + // We need to add the parameter as a child declaration of + // the accessor we are building. + // + synParamDecl->parentDecl = synAccessorDecl; + synAccessorDecl->members.add(synParamDecl); + + // For each paramter, we will create an argument expression + // to represent it in the body of the accessor. + // + auto synArg = m_astBuilder->create<VarExpr>(); + synArg->declRef = makeDeclRef(synParamDecl); + synArg->type = paramType; + synArgs.add(synArg); + } + + // We need to create a `this` expression to be used in the body + // of the synthesized accessor. + // + // TODO: if we ever allow `static` properties or subscripts, + // we will need to handle that case here, by *not* creating + // a `this` expression. + // + ThisExpr* synThis = m_astBuilder->create<ThisExpr>(); + + // The type of `this` in our accessor will be the type for + // which we are synthesizing a conformance. + // + synThis->type.type = context->conformingType; + + // A `get` accessor should default to an immutable `this`, + // while other accessors default to mutable `this`. + // + // TODO: If we ever add other kinds of accessors, we will + // need to check that this assumption stays valid. + // + synThis->type.isLeftValue = true; + if(as<GetterDecl>(requiredAccessorDeclRef)) + synThis->type.isLeftValue = false; + + // If the accessor requirement is `[nonmutating]` then our + // synthesized accessor should be too, and also the `this` + // parameter should *not* be an l-value. + // + if( requiredAccessorDeclRef.getDecl()->hasModifier<NonmutatingAttribute>() ) + { + synThis->type.isLeftValue = false; + + auto synAttr = m_astBuilder->create<NonmutatingAttribute>(); + synAccessorDecl->modifiers.first = synAttr; + } + // + // Note: we don't currently support `[mutating] get` accessors, + // but the desired behavior in that case is clear, so we go + // ahead and future-proof this code a bit: + // + else if( requiredAccessorDeclRef.getDecl()->hasModifier<MutatingAttribute>() ) + { + synThis->type.isLeftValue = true; + + auto synAttr = m_astBuilder->create<MutatingAttribute>(); + synAccessorDecl->modifiers.first = synAttr; + } + + // We are going to synthesize an expression and then perform + // semantic checking on it, but if there are semantic errors + // we do *not* want to report them to the user as such, and + // instead want the result to be a failure to synthesize + // a valid witness. + // + // We will buffer up diagnostics into a temporary sink and + // then throw them away when we are done. + // + // TODO: This behavior might be something we want to make + // into a more fundamental capability of `DiagnosticSink` and/or + // `SemanticsVisitor` so that code can push/pop the emission + // of diagnostics more easily. + // + DiagnosticSink* savedSink = m_shared->m_sink; + DiagnosticSink tempSink(savedSink->getSourceManager()); + m_shared->m_sink = &tempSink; + + // We start by constructing an expression that represents + // `this.name` where `name` is the name of the required + // member. The caller already passed in a `lookupResult` + // that should indicate all the declarations found by + // looking up `name`, so we can start with that. + // + // TODO: Note that there are many cases for member lookup + // that are not handled just by using `createLookupResultExpr` + // because they are currently being special-cased (the most + // notable cases are swizzles, as well as lookup of static + // members in types). + // + // The main result here is that we will not be able to synthesize + // a requirement for a built-in scalar/vector/matrix type to + // a property with a name like `.xy` based on the presence of + // swizles, even though it seems like such a thing should Just Work. + // + // If this is important we could "fix" it by allowing this + // code to dispatch to the special-case logic used when doing + // semantic checking for member expressions. + // + // Note: an alternative would be to change the stdlib declarations + // of vectors/matrices so that all the swizzles are defined as + // `property` declarations. There are some C++ math libraries (like GLM) + // that implement swizzle syntax by a similar approach of statically + // enumerating all possible swizzles. The down-side to such an + // approach is that the combinatorial space of swizzles is quite + // large (especially for matrices) so that supporting them via + // general-purpose language features is unlikely to be as efficient + // as special-case logic. + // + auto synMemberRef = createLookupResultExpr( + requiredMemberDeclRef.getName(), + lookupResult, + synThis, + requiredMemberDeclRef.getLoc()); + + // The body of the accessor will depend on the class of the accessor + // we are synthesizing (e.g., `get` vs. `set`). + // + Stmt* synBodyStmt = nullptr; + if( as<GetterDecl>(requiredAccessorDeclRef) ) + { + // A `get` accessor will simply perform: + // + // return this.name; + // + // which involves coercing the member access `this.name` to + // the expected type of the property. + // + auto coercedMemberRef = coerce(propertyType, synMemberRef); + auto synReturn = m_astBuilder->create<ReturnStmt>(); + synReturn->expression = coercedMemberRef; + + synBodyStmt = synReturn; + } + else if( as<SetterDecl>(requiredAccessorDeclRef) ) + { + // We expect all `set` accessors to have a single argument, + // but we will defensively bail out if that is somehow + // not the case. + // + SLANG_ASSERT(synArgs.getCount() == 1); + if(synArgs.getCount() != 1) + return false; + + // A `set` accessor will simply perform: + // + // this.name = newValue; + // + // which involves creating and checking an assignment + // expression. + + auto synAssign = m_astBuilder->create<AssignExpr>(); + synAssign->left = synMemberRef; + synAssign->right = synArgs[0]; + + auto synCheckedAssign = checkAssignWithCheckedOperands(synAssign); + + auto synExprStmt = m_astBuilder->create<ExpressionStmt>(); + synExprStmt->expression = synCheckedAssign; + + synBodyStmt = synExprStmt; + } + else + { + // While there are other kinds of accessors than `get` and `set`, + // those are currently only reserved for stdlib-internal use. + // We will not bother with synthesis for those cases. + // + return false; + } + + // We restore the semantic checking state that was in place before + // we checked the synthesized accessor body, and then bail out + // if we ran into any errors (meaning that the synthesized accessor + // is not usable). + // + // TODO: If there were *warnings* emitted to the sink, it would probably + // be good to show those warnings to the user, since they might indicate + // real issues. E.g., with the current logic a `float` field could + // satisfying an `int` property requirement, but the user would probably + // want to be warned when they do such a thing. + // + m_shared->m_sink = savedSink; + if(tempSink.getErrorCount() != 0) + return false; + + synAccessorDecl->body = synBodyStmt; + + synAccessorDecl->parentDecl = synPropertyDecl; + synPropertyDecl->members.add(synAccessorDecl); + + // If synthesis of an accessor worked, then we will record it into + // a local dictionary. We do *not* install the accessor into the + // witness table yet, because it is possible that synthesis will + // succeed for some accessors but not others, and we don't want + // to leave the witness table in a state where a requirement is + // "partially satisfied." + // + mapRequiredAccessorToSynAccessor.Add(requiredAccessorDeclRef, synAccessorDecl); + } + + synPropertyDecl->parentDecl = context->parentDecl; + + // Once our synthesized declaration is complete, we need + // to install it as the witness that satifies the given + // requirement. + // + // Subsequent code generation should not be able to tell the + // difference between our synthetic property and a hand-written + // one with the same behavior. + // + for(auto p : mapRequiredAccessorToSynAccessor) + { + witnessTable->requirementDictionary.Add(p.Key, RequirementWitness(makeDeclRef(p.Value))); + } + witnessTable->requirementDictionary.Add(requiredMemberDeclRef, + RequirementWitness(makeDeclRef(synPropertyDecl))); + return true; + } + + bool SemanticsVisitor::trySynthesizeRequirementWitness( ConformanceCheckingContext* context, LookupResult const& lookupResult, @@ -1778,6 +2236,15 @@ namespace Slang witnessTable); } + if( auto requiredPropertyDeclRef = requiredMemberDeclRef.as<PropertyDecl>() ) + { + return trySynthesizePropertyRequirementWitness( + context, + lookupResult, + requiredPropertyDeclRef, + witnessTable); + } + // TODO: There are other kinds of requirements for which synthesis should // be possible: // @@ -1785,13 +2252,7 @@ namespace Slang // using an approach similar to what is used for methods. // // * We should be able to synthesize subscripts with different - // signatures (taking into account default parameters) and even - // different accessors (e.g., synthesizing the `get` and `set` - // accessors from a `ref` accessor) - // - // * When we support property declarations, it should be possible - // to synthesize a property requirement using a field of the - // same name. + // signatures (taking into account default parameters). // // * For specific kinds of generic requirements, we should be able // to wrap the synthesis of the inner declaration in synthesis diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 42842995d..4268ad99e 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -1135,13 +1135,11 @@ namespace Slang } } - Expr* SemanticsExprVisitor::visitAssignExpr(AssignExpr* expr) + Expr* SemanticsVisitor::checkAssignWithCheckedOperands(AssignExpr* expr) { - expr->left = CheckExpr(expr->left); - auto type = expr->left->type; - expr->right = coerce(type, CheckTerm(expr->right)); + expr->right = coerce(type, expr->right); if (!type.isLeftValue) { @@ -1165,6 +1163,13 @@ namespace Slang return expr; } + Expr* SemanticsExprVisitor::visitAssignExpr(AssignExpr* expr) + { + expr->left = CheckExpr(expr->left); + expr->right = CheckTerm(expr->right); + return checkAssignWithCheckedOperands(expr); + } + Expr* SemanticsVisitor::CheckExpr(Expr* expr) { auto term = CheckTerm(expr); @@ -1846,11 +1851,13 @@ namespace Slang return expr; } - Expr* SemanticsExprVisitor::visitMemberExpr(MemberExpr * expr) + Expr* SemanticsVisitor::checkBaseForMemberExpr(Expr* inBaseExpr) { - expr->baseExpression = CheckExpr(expr->baseExpression); + auto baseExpr = inBaseExpr; - expr->baseExpression = MaybeDereference(expr->baseExpression); + baseExpr = CheckExpr(baseExpr); + + baseExpr = MaybeDereference(baseExpr); // If the base of the member lookup has an interface type // *without* a suitable this-type substitution, then we are @@ -1858,13 +1865,13 @@ namespace Slang // and we should "open" the existential here so that we // can expose its structure. // - expr->baseExpression = maybeOpenExistential(expr->baseExpression); + baseExpr = maybeOpenExistential(baseExpr); // Handle the case of an overloaded base expression // here, in case we can use the name of the member to // disambiguate which of the candidates is meant, or if // we can return an overloaded result. - if (auto overloadedExpr = as<OverloadedExpr>(expr->baseExpression)) + if (auto overloadedExpr = as<OverloadedExpr>(baseExpr)) { if (overloadedExpr->base) { @@ -1884,8 +1891,8 @@ namespace Slang } if (filteredLookupResult.items.getCount() == 1) filteredLookupResult.item = filteredLookupResult.items.getFirst(); - expr->baseExpression = createLookupResultExpr( - expr->name, + baseExpr = createLookupResultExpr( + overloadedExpr->name, filteredLookupResult, overloadedExpr->base, overloadedExpr->loc); @@ -1893,6 +1900,12 @@ namespace Slang // TODO: handle other cases of OverloadedExpr that need filtering. } + return baseExpr; + } + + Expr* SemanticsExprVisitor::visitMemberExpr(MemberExpr * expr) + { + expr->baseExpression = checkBaseForMemberExpr(expr->baseExpression); auto & baseType = expr->baseExpression->type; // Note: Checking for vector types before declaration-reference types, diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 19f5553c7..7907ef23b 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -737,6 +737,15 @@ namespace Slang DeclRef<CallableDecl> requiredMemberDeclRef, RefPtr<WitnessTable> witnessTable); + bool doesAccessorMatchRequirement( + DeclRef<AccessorDecl> satisfyingMemberDeclRef, + DeclRef<AccessorDecl> requiredMemberDeclRef); + + bool doesPropertyMatchRequirement( + DeclRef<PropertyDecl> satisfyingMemberDeclRef, + DeclRef<PropertyDecl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable); + bool doesGenericSignatureMatchRequirement( DeclRef<GenericDecl> genDecl, DeclRef<GenericDecl> requirementGenDecl, @@ -783,6 +792,17 @@ namespace Slang DeclRef<FuncDecl> requiredMemberDeclRef, RefPtr<WitnessTable> witnessTable); + /// Attempt to synthesize a property that can satisfy `requiredMemberDeclRef` using `lookupResult`. + /// + /// On success, installs the syntethesized method in `witnessTable` and returns `true`. + /// Otherwise, returns `false`. + /// + bool trySynthesizePropertyRequirementWitness( + ConformanceCheckingContext* context, + LookupResult const& lookupResult, + DeclRef<PropertyDecl> requiredMemberDeclRef, + RefPtr<WitnessTable> witnessTable); + /// Attempt to synthesize a declartion that can satisfy `requiredMemberDeclRef` using `lookupResult`. /// /// On success, installs the syntethesized declaration in `witnessTable` and returns `true`. @@ -1453,6 +1473,9 @@ namespace Slang Type* baseElementType, IntVal* baseElementCount); + /// Perform semantic checking of an assignment where the operands have already been checked. + Expr* checkAssignWithCheckedOperands(AssignExpr* expr); + // Look up a static member // @param expr Can be StaticMemberExpr or MemberExpr // @param baseExpression Is the underlying type expression determined from resolving expr @@ -1460,6 +1483,9 @@ namespace Slang Expr* visitStaticMemberExpr(StaticMemberExpr* expr); + /// Perform checking operations required for the "base" expression of a member-reference like `base.someField` + Expr* checkBaseForMemberExpr(Expr* baseExpr); + Expr* lookupMemberResultFailure( DeclRefExpr* expr, QualType const& baseType); diff --git a/tests/language-feature/properties/property-in-interface.slang b/tests/language-feature/properties/property-in-interface.slang new file mode 100644 index 000000000..ba6d5eaa9 --- /dev/null +++ b/tests/language-feature/properties/property-in-interface.slang @@ -0,0 +1,47 @@ +// property-in-interface.slang + +//TEST(compute):COMPARE_COMPUTE: + +// Test that interfaces can include property declarations. + +interface ICell +{ + property value : int { get; set; } +} + +struct MyCell : ICell +{ + var _data : int; + + property value : int { get { return _data; } set(newValue) { _data = newValue; } } +} + +struct YourCell : ICell +{ + int value; +} + +int helper<C : ICell>(C cell) +{ + cell.value = cell.value + 1; + return cell.value; +} + +int test(int value) +{ + MyCell myCell = { value+1 }; + YourCell yourCell = { value }; + return helper(myCell)*16 + helper(yourCell); +} + +//TEST_INPUT:ubuffer(data=[0 1 2 3], stride=4):out,name=outputBuffer +RWStructuredBuffer<int> outputBuffer : register(u0); + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint tid = dispatchThreadID.x; + int inVal = outputBuffer[tid]; + int outVal = test(inVal); + outputBuffer[tid] = outVal; +} diff --git a/tests/language-feature/properties/property-in-interface.slang.expected.txt b/tests/language-feature/properties/property-in-interface.slang.expected.txt new file mode 100644 index 000000000..ba2ec282d --- /dev/null +++ b/tests/language-feature/properties/property-in-interface.slang.expected.txt @@ -0,0 +1,4 @@ +21 +32 +43 +54 |
