diff options
Diffstat (limited to 'source')
| -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 |
4 files changed, 530 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); |
