#include "syntax-visitors.h" #include "lookup.h" #include "compiler.h" #include "visitor.h" #include "../core/secure-crt.h" #include namespace Slang { // A flat representation of basic types (scalars, vectors and matrices) // that can be used as lookup key in caches struct BasicTypeKey { union { struct { unsigned char type : 4; unsigned char dim1 : 2; unsigned char dim2 : 2; } data; unsigned char aggVal; }; bool fromType(Type* typeIn) { aggVal = 0; if (auto basicType = typeIn->AsBasicType()) { data.type = (unsigned char)basicType->baseType; data.dim1 = data.dim2 = 0; } else if (auto vectorType = typeIn->AsVectorType()) { if (auto elemCount = vectorType->elementCount.As()) { data.dim1 = elemCount->value - 1; data.type = (unsigned char)vectorType->elementType->AsBasicType()->baseType; data.dim2 = 0; } else return false; } else if (auto matrixType = typeIn->AsMatrixType()) { if (auto elemCount1 = dynamic_cast(matrixType->getRowCount())) { if (auto elemCount2 = dynamic_cast(matrixType->getColumnCount())) { data.type = (unsigned char)matrixType->getElementType()->AsBasicType()->baseType; data.dim1 = elemCount1->value - 1; data.dim2 = elemCount2->value - 1; } } else return false; } else return false; return true; } }; struct BasicTypeKeyPair { BasicTypeKey type1, type2; bool operator == (BasicTypeKeyPair p) { return type1.aggVal == p.type1.aggVal && type2.aggVal == p.type2.aggVal; } int GetHashCode() { return combineHash(type1.aggVal, type2.aggVal); } }; struct OverloadCandidate { enum class Flavor { Func, Generic, UnspecializedGeneric, }; Flavor flavor; enum class Status { GenericArgumentInferenceFailed, Unchecked, ArityChecked, FixityChecked, TypeChecked, DirectionChecked, Appicable, }; Status status = Status::Unchecked; // Reference to the declaration being applied LookupResultItem item; // The type of the result expression if this candidate is selected RefPtr resultType; // A system for tracking constraints introduced on generic parameters // ConstraintSystem constraintSystem; // How much conversion cost should be considered for this overload, // when ranking candidates. ConversionCost conversionCostSum = kConversionCost_None; // When required, a candidate can store a pre-checked list of // arguments so that we don't have to repeat work across checking // phases. Currently this is only needed for generics. RefPtr subst; }; struct OperatorOverloadCacheKey { IROp operatorName; BasicTypeKey args[2]; bool operator == (OperatorOverloadCacheKey key) { return operatorName == key.operatorName && args[0].aggVal == key.args[0].aggVal && args[1].aggVal == key.args[1].aggVal; } int GetHashCode() { return ((int)(UInt64)(void*)(operatorName) << 16) ^ (args[0].aggVal << 8) ^ (args[1].aggVal); } bool fromOperatorExpr(OperatorExpr* opExpr) { // First, lets see if the argument types are ones // that we can encode in our space of keys. args[0].aggVal = 0; args[1].aggVal = 0; if (opExpr->Arguments.Count() > 2) return false; for (UInt i = 0; i < opExpr->Arguments.Count(); i++) { if (!args[i].fromType(opExpr->Arguments[i]->type.Ptr())) return false; } // Next, lets see if we can find an intrinsic opcode // attached to an overloaded definition (filtered for // definitions that could conceivably apply to us). // // TODO: This should really be pased on the operator name // plus fixity, rather than the intrinsic opcode... // // We will need to reject postfix definitions for prefix // operators, and vice versa, to ensure things work. // auto prefixExpr = opExpr->As(); auto postfixExpr = opExpr->As(); if (auto overloadedBase = opExpr->FunctionExpr->As()) { for(auto item : overloadedBase->lookupResult2 ) { // Look at a candidate definition to be called and // see if it gives us a key to work with. // Decl* funcDecl = overloadedBase->lookupResult2.item.declRef.decl; if (auto genDecl = funcDecl->As()) funcDecl = genDecl->inner.Ptr(); // Reject definitions that have the wrong fixity. // if(prefixExpr && !funcDecl->FindModifier()) continue; if(postfixExpr && !funcDecl->FindModifier()) continue; if (auto intrinsicOp = funcDecl->FindModifier()) { operatorName = intrinsicOp->op; return true; } } } return false; } }; struct TypeCheckingCache { Dictionary resolvedOperatorOverloadCache; Dictionary conversionCostCache; }; TypeCheckingCache* Session::getTypeCheckingCache() { if (!typeCheckingCache) typeCheckingCache = new TypeCheckingCache(); return typeCheckingCache; } void Session::destroyTypeCheckingCache() { delete typeCheckingCache; typeCheckingCache = nullptr; } bool IsNumeric(BaseType t) { return t == BaseType::Int || t == BaseType::Float || t == BaseType::UInt; } String TranslateHLSLTypeNames(String name) { if (name == "float2" || name == "half2") return "vec2"; else if (name == "float3" || name == "half3") return "vec3"; else if (name == "float4" || name == "half4") return "vec4"; else if (name == "half") return "float"; else if (name == "int2") return "ivec2"; else if (name == "int3") return "ivec3"; else if (name == "int4") return "ivec4"; else if (name == "uint2") return "uvec2"; else if (name == "uint3") return "uvec3"; else if (name == "uint4") return "uvec4"; else if (name == "float3x3" || name == "half3x3") return "mat3"; else if (name == "float4x4" || name == "half4x4") return "mat4"; else return name; } enum class CheckingPhase { Header, Body }; struct SemanticsVisitor : ExprVisitor> , StmtVisitor , DeclVisitor { CheckingPhase checkingPhase = CheckingPhase::Header; DeclCheckState getCheckedState() { if (checkingPhase == CheckingPhase::Body) return DeclCheckState::Checked; else return DeclCheckState::CheckedHeader; } DiagnosticSink* sink = nullptr; DiagnosticSink* getSink() { return sink; } // ModuleDecl * program = nullptr; FuncDecl * function = nullptr; CompileRequest* request = nullptr; TranslationUnitRequest* translationUnit = nullptr; SourceLanguage getSourceLanguage() { return translationUnit->sourceLanguage; } // lexical outer statements List outerStmts; // We need to track what has been `import`ed, // to avoid importing the same thing more than once // // TODO: a smarter approach might be to filter // out duplicate references during lookup. HashSet importedModules; public: SemanticsVisitor( DiagnosticSink* sink, CompileRequest* request, TranslationUnitRequest* translationUnit) : sink(sink) , request(request) , translationUnit(translationUnit) { } CompileRequest* getCompileRequest() { return request; } TranslationUnitRequest* getTranslationUnit() { return translationUnit; } Session* getSession() { return getCompileRequest()->mSession; } public: // Translate Types RefPtr typeResult; RefPtr TranslateTypeNodeImpl(const RefPtr & node) { if (!node) return nullptr; auto expr = CheckTerm(node); expr = ExpectATypeRepr(expr); return expr; } RefPtr ExtractTypeFromTypeRepr(const RefPtr& typeRepr) { if (!typeRepr) return nullptr; if (auto typeType = typeRepr->type->As()) { return typeType->type; } return getSession()->getErrorType(); } RefPtr TranslateTypeNode(const RefPtr & node) { if (!node) return nullptr; auto typeRepr = TranslateTypeNodeImpl(node); return ExtractTypeFromTypeRepr(typeRepr); } TypeExp TranslateTypeNodeForced(TypeExp const& typeExp) { auto typeRepr = TranslateTypeNodeImpl(typeExp.exp); TypeExp result; result.exp = typeRepr; result.type = ExtractTypeFromTypeRepr(typeRepr); return result; } TypeExp TranslateTypeNode(TypeExp const& typeExp) { // HACK(tfoley): It seems that in some cases we end up re-checking // syntax that we've already checked. We need to root-cause that // issue, but for now a quick fix in this case is to early // exist if we've already got a type associated here: if (typeExp.type) { return typeExp; } return TranslateTypeNodeForced(typeExp); } RefPtr getExprDeclRefType(Expr * expr) { if (auto typetype = expr->type->As()) return typetype->type.As(); else return expr->type->As(); } /// Is `decl` usable as a static member? bool isDeclUsableAsStaticMember( Decl* decl) { if(decl->HasModifier()) return true; if(decl->As()) return true; if(decl->As()) return true; if(decl->As()) return true; if(decl->As()) return true; return false; } /// Is `item` usable as a static member? bool isUsableAsStaticMember( LookupResultItem const& item) { // There's a bit of a gotcha here, because a lookup result // item might include "breadcrumbs" that indicate more steps // along the lookup path. As a result it isn't always // valid to just check whether the final decl is usable // as a static member, because it might not even be a // member of the thing we are trying to work with. // Decl* decl = item.declRef.getDecl(); for(auto bb = item.breadcrumbs; bb; bb = bb->next) { switch(bb->kind) { // In case lookup went through a `__transparent` member, // we are interested in the static-ness of that transparent // member, and *not* the static-ness of whatever was inside // of it. // // TODO: This would need some work if we ever had // transparent *type* members. // case LookupResultItem::Breadcrumb::Kind::Member: decl = bb->declRef.getDecl(); break; // TODO: Are there any other cases that need special-case // handling here? default: break; } } // Okay, we've found the declaration we should actually // be checking, so lets validate that. return isDeclUsableAsStaticMember(decl); } RefPtr ConstructDeclRefExpr( DeclRef declRef, RefPtr baseExpr, SourceLoc loc) { // Compute the type that this declaration reference will have in context. // auto type = GetTypeForDeclRef(declRef); // Construct an appropriate expression based on teh structured of // the declaration reference. // if (baseExpr) { // If there was a base expression, we will have some kind of // member expression. // if (baseExpr->type->As()) { auto expr = new StaticMemberExpr(); expr->loc = loc; expr->type = type; expr->BaseExpression = baseExpr; expr->name = declRef.GetName(); expr->declRef = declRef; return expr; } else { // If the base expression wasn't a type, then this // is a normal member expression. // auto expr = new MemberExpr(); expr->loc = loc; expr->type = type; expr->BaseExpression = baseExpr; expr->name = declRef.GetName(); expr->declRef = declRef; // When referring to a member through an expression, // the result is only an l-value if both the base // expression and the member agree that it should be. // // We have already used the `QualType` from the member // above (that is `type`), so we need to take the // l-value status of the base expression into account now. if(!baseExpr->type.IsLeftValue) { expr->type.IsLeftValue = false; } return expr; } } else { // If there is no base expression, then the result must // be an ordinary variable expression. // auto expr = new VarExpr(); expr->loc = loc; expr->name = declRef.GetName(); expr->type = type; expr->declRef = declRef; return expr; } } RefPtr ConstructDerefExpr( RefPtr base, SourceLoc loc) { auto ptrLikeType = base->type->As(); SLANG_ASSERT(ptrLikeType); auto derefExpr = new DerefExpr(); derefExpr->loc = loc; derefExpr->base = base; derefExpr->type = QualType(ptrLikeType->elementType); // TODO(tfoley): handle l-value status here return derefExpr; } RefPtr createImplicitThisMemberExpr( Type* type, SourceLoc loc) { RefPtr expr = new ThisExpr(); expr->type = type; expr->loc = loc; return expr; } RefPtr ConstructLookupResultExpr( LookupResultItem const& item, RefPtr baseExpr, SourceLoc loc) { // If we collected any breadcrumbs, then these represent // additional segments of the lookup path that we need // to expand here. auto bb = baseExpr; for (auto breadcrumb = item.breadcrumbs; breadcrumb; breadcrumb = breadcrumb->next) { switch (breadcrumb->kind) { case LookupResultItem::Breadcrumb::Kind::Member: bb = ConstructDeclRefExpr(breadcrumb->declRef, bb, loc); break; case LookupResultItem::Breadcrumb::Kind::Deref: bb = ConstructDerefExpr(bb, loc); break; case LookupResultItem::Breadcrumb::Kind::Constraint: { // TODO: do we need to make something more // explicit here? bb = ConstructDeclRefExpr( breadcrumb->declRef, bb, loc); } break; case LookupResultItem::Breadcrumb::Kind::This: { // We expect a `this` to always come // at the start of a chain. SLANG_ASSERT(bb == nullptr); // The member was looked up via a `this` expression, // so we need to create one here. if (auto extensionDeclRef = breadcrumb->declRef.As()) { bb = createImplicitThisMemberExpr( GetTargetType(extensionDeclRef), loc); } else { auto type = DeclRefType::Create(getSession(), breadcrumb->declRef); bb = createImplicitThisMemberExpr( type, loc); } } break; default: SLANG_UNREACHABLE("all cases handle"); } } return ConstructDeclRefExpr(item.declRef, bb, loc); } RefPtr createLookupResultExpr( LookupResult const& lookupResult, RefPtr baseExpr, SourceLoc loc) { if (lookupResult.isOverloaded()) { auto overloadedExpr = new OverloadedExpr(); overloadedExpr->loc = loc; overloadedExpr->type = QualType( getSession()->getOverloadedType()); overloadedExpr->base = baseExpr; overloadedExpr->lookupResult2 = lookupResult; return overloadedExpr; } else { return ConstructLookupResultExpr(lookupResult.item, baseExpr, loc); } } RefPtr ResolveOverloadedExpr(RefPtr overloadedExpr, LookupMask mask) { auto lookupResult = overloadedExpr->lookupResult2; SLANG_RELEASE_ASSERT(lookupResult.isValid() && lookupResult.isOverloaded()); // Take the lookup result we had, and refine it based on what is expected in context. lookupResult = refineLookup(lookupResult, mask); if (!lookupResult.isValid()) { // If we didn't find any symbols after filtering, then just // use the original and report errors that way return overloadedExpr; } if (lookupResult.isOverloaded()) { // We had an ambiguity anyway, so report it. getSink()->diagnose(overloadedExpr, Diagnostics::ambiguousReference, lookupResult.items[0].declRef.GetName()); for(auto item : lookupResult.items) { String declString = getDeclSignatureString(item); getSink()->diagnose(item.declRef, Diagnostics::overloadCandidate, declString); } // TODO(tfoley): should we construct a new ErrorExpr here? return CreateErrorExpr(overloadedExpr); } // otherwise, we had a single decl and it was valid, hooray! return ConstructLookupResultExpr(lookupResult.item, overloadedExpr->base, overloadedExpr->loc); } RefPtr ExpectATypeRepr(RefPtr expr) { if (auto overloadedExpr = expr.As()) { expr = ResolveOverloadedExpr(overloadedExpr, LookupMask::type); } if (auto typeType = expr->type.type->As()) { return expr; } else if (auto errorType = expr->type.type->As()) { return expr; } getSink()->diagnose(expr, Diagnostics::unimplemented, "expected a type"); return CreateErrorExpr(expr); } RefPtr ExpectAType(RefPtr expr) { auto typeRepr = ExpectATypeRepr(expr); if (auto typeType = typeRepr->type->As()) { return typeType->type; } return getSession()->getErrorType(); } RefPtr ExtractGenericArgType(RefPtr exp) { return ExpectAType(exp); } RefPtr ExtractGenericArgInteger(RefPtr exp) { return CheckIntegerConstantExpression(exp.Ptr()); } RefPtr ExtractGenericArgVal(RefPtr exp) { if (auto overloadedExpr = exp.As()) { // assume that if it is overloaded, we want a type exp = ResolveOverloadedExpr(overloadedExpr, LookupMask::type); } if (auto typeType = exp->type->As()) { return typeType->type; } else if (auto errorType = exp->type->As()) { return exp->type.type; } else { return ExtractGenericArgInteger(exp); } } // Construct a type reprsenting the instantiation of // the given generic declaration for the given arguments. // The arguments should already be checked against // the declaration. RefPtr InstantiateGenericType( DeclRef genericDeclRef, List> const& args) { RefPtr subst = new GenericSubstitution(); subst->genericDecl = genericDeclRef.getDecl(); subst->outer = genericDeclRef.substitutions.substitutions; for (auto argExpr : args) { subst->args.Add(ExtractGenericArgVal(argExpr)); } DeclRef innerDeclRef; innerDeclRef.decl = GetInner(genericDeclRef); innerDeclRef.substitutions = SubstitutionSet(subst); return DeclRefType::Create( getSession(), innerDeclRef); } // This routine is a bottleneck for all declaration checking, // so that we can add some quality-of-life features for users // in cases where the compiler crashes void dispatchDecl(DeclBase* decl) { try { DeclVisitor::dispatch(decl); } // Don't emit any context message for an explicit `AbortCompilationException` // because it should only happen when an error is already emitted. catch(AbortCompilationException&) { throw; } catch(...) { getCompileRequest()->noteInternalErrorLoc(decl->loc); throw; } } void dispatchStmt(Stmt* stmt) { try { StmtVisitor::dispatch(stmt); } catch(AbortCompilationException&) { throw; } catch(...) { getCompileRequest()->noteInternalErrorLoc(stmt->loc); throw; } } void dispatchExpr(Expr* expr) { try { ExprVisitor::dispatch(expr); } catch(AbortCompilationException&) { throw; } catch(...) { getCompileRequest()->noteInternalErrorLoc(expr->loc); throw; } } // Make sure a declaration has been checked, so we can refer to it. // Note that this may lead to us recursively invoking checking, // so this may not be the best way to handle things. void EnsureDecl(RefPtr decl, DeclCheckState state) { if (decl->IsChecked(state)) return; if (decl->checkState == DeclCheckState::CheckingHeader) { // We tried to reference the same declaration while checking it! // // TODO: we should ideally be tracking a "chain" of declarations // being checked on the stack, so that we can report the full // chain that leads from this declaration back to itself. // sink->diagnose(decl, Diagnostics::cyclicReference, decl); return; } // Hack: if we are somehow referencing a local variable declaration // before the line of code that defines it, then we need to diagnose // an error. // // TODO: The right answer is that lookup should have been performed in // the scope that was in place *before* the variable was declared, but // this is a quick fix that at least alerts the user to how we are // interpreting their code. if (auto varDecl = decl.As()) { if (auto parenScope = varDecl->ParentDecl->As()) { // TODO: This diagnostic should be emitted on the line that is referencing // the declaration. That requires `EnsureDecl` to take the requesting // location as a parameter. sink->diagnose(decl, Diagnostics::localVariableUsedBeforeDeclared, decl); return; } } if (DeclCheckState::CheckingHeader > decl->checkState) { decl->SetCheckState(DeclCheckState::CheckingHeader); } // Use visitor pattern to dispatch to correct case dispatchDecl(decl); if(state > decl->checkState) { decl->SetCheckState(state); } } void EnusreAllDeclsRec(RefPtr decl) { checkDecl(decl); if (auto containerDecl = decl.As()) { for (auto m : containerDecl->Members) { EnusreAllDeclsRec(m); } } } // A "proper" type is one that can be used as the type of an expression. // Put simply, it can be a concrete type like `int`, or a generic // type that is applied to arguments, like `Texture2D`. // The type `void` is also a proper type, since we can have expressions // that return a `void` result (e.g., many function calls). // // A "non-proper" type is any type that can't actually have values. // A simple example of this in C++ is `std::vector` - you can't have // a value of this type. // // Part of what this function does is give errors if somebody tries // to use a non-proper type as the type of a variable (or anything // else that needs a proper type). // // The other thing it handles is the fact that HLSL lets you use // the name of a non-proper type, and then have the compiler fill // in the default values for its type arguments (e.g., a variable // given type `Texture2D` will actually have type `Texture2D`). bool CoerceToProperTypeImpl( TypeExp const& typeExp, RefPtr* outProperType, DiagnosticSink* diagSink) { Type* type = typeExp.type.Ptr(); if(!type && typeExp.exp) { if(auto typeType = typeExp.exp->type.type.As()) { type = typeType->type; } } if (auto genericDeclRefType = type->As()) { // We are using a reference to a generic declaration as a concrete // type. This means we should substitute in any default parameter values // if they are available. // // TODO(tfoley): A more expressive type system would substitute in // "fresh" variables and then solve for their values... // auto genericDeclRef = genericDeclRefType->GetDeclRef(); checkDecl(genericDeclRef.decl); List> args; for (RefPtr member : genericDeclRef.getDecl()->Members) { if (auto typeParam = member.As()) { if (!typeParam->initType.exp) { if (diagSink) { diagSink->diagnose(typeExp.exp.Ptr(), Diagnostics::unimplemented, "can't fill in default for generic type parameter"); *outProperType = getSession()->getErrorType(); } return false; } // TODO: this is one place where syntax should get cloned! if(outProperType) args.Add(typeParam->initType.exp); } else if (auto valParam = member.As()) { if (!valParam->initExpr) { if (diagSink) { diagSink->diagnose(typeExp.exp.Ptr(), Diagnostics::unimplemented, "can't fill in default for generic type parameter"); *outProperType = getSession()->getErrorType(); } return false; } // TODO: this is one place where syntax should get cloned! if(outProperType) args.Add(valParam->initExpr); } else { // ignore non-parameter members } } if (outProperType) { *outProperType = InstantiateGenericType(genericDeclRef, args); } return true; } else { // default case: we expect this to already be a proper type if (outProperType) { *outProperType = type; } return true; } } TypeExp CoerceToProperType(TypeExp const& typeExp) { TypeExp result = typeExp; CoerceToProperTypeImpl(typeExp, &result.type, getSink()); return result; } TypeExp tryCoerceToProperType(TypeExp const& typeExp) { TypeExp result = typeExp; if(!CoerceToProperTypeImpl(typeExp, &result.type, nullptr)) return TypeExp(); return result; } // Check a type, and coerce it to be proper TypeExp CheckProperType(TypeExp typeExp) { return CoerceToProperType(TranslateTypeNode(typeExp)); } // For our purposes, a "usable" type is one that can be // used to declare a function parameter, variable, etc. // These turn out to be all the proper types except // `void`. // // TODO(tfoley): consider just allowing `void` as a // simple example of a "unit" type, and get rid of // this check. TypeExp CoerceToUsableType(TypeExp const& typeExp) { TypeExp result = CoerceToProperType(typeExp); Type* type = result.type.Ptr(); if (auto basicType = type->As()) { // TODO: `void` shouldn't be a basic type, to make this easier to avoid if (basicType->baseType == BaseType::Void) { // TODO(tfoley): pick the right diagnostic message getSink()->diagnose(result.exp.Ptr(), Diagnostics::invalidTypeVoid); result.type = getSession()->getErrorType(); return result; } } return result; } // Check a type, and coerce it to be usable TypeExp CheckUsableType(TypeExp typeExp) { return CoerceToUsableType(TranslateTypeNode(typeExp)); } RefPtr CheckTerm(RefPtr term) { if (!term) return nullptr; return ExprVisitor::dispatch(term); } RefPtr CreateErrorExpr(Expr* expr) { expr->type = QualType(getSession()->getErrorType()); return expr; } bool IsErrorExpr(RefPtr expr) { // TODO: we may want other cases here... if (auto errorType = expr->type->As()) return true; return false; } // Capture the "base" expression in case this is a member reference RefPtr GetBaseExpr(RefPtr expr) { if (auto memberExpr = expr.As()) { return memberExpr->BaseExpression; } else if(auto overloadedExpr = expr.As()) { return overloadedExpr->base; } return nullptr; } public: bool ValuesAreEqual( RefPtr left, RefPtr right) { if(left == right) return true; if(auto leftConst = left.As()) { if(auto rightConst = right.As()) { return leftConst->value == rightConst->value; } } if(auto leftVar = left.As()) { if(auto rightVar = right.As()) { return leftVar->declRef.Equals(rightVar->declRef); } } return false; } // Compute the cost of using a particular declaration to // perform implicit type conversion. ConversionCost getImplicitConversionCost( Decl* decl) { if(auto modifier = decl->FindModifier()) { return modifier->cost; } return kConversionCost_Explicit; } // Central engine for implementing implicit coercion logic bool TryCoerceImpl( RefPtr toType, // the target type for conversion RefPtr* outToExpr, // (optional) a place to stuff the target expression RefPtr fromType, // the source type for the conversion RefPtr fromExpr, // the source expression ConversionCost* outCost) // (optional) a place to stuff the conversion cost { // Easy case: the types are equal if (toType->Equals(fromType)) { if (outToExpr) *outToExpr = fromExpr; if (outCost) *outCost = kConversionCost_None; return true; } // If either type is an error, then let things pass. if (toType->As() || fromType->As()) { if (outToExpr) *outToExpr = CreateImplicitCastExpr(toType, fromExpr); if (outCost) *outCost = kConversionCost_None; return true; } // Coercion from an initializer list is allowed for many types if( auto fromInitializerListExpr = fromExpr.As()) { auto argCount = fromInitializerListExpr->args.Count(); // In the case where we need to build a reuslt expression, // we will collect the new arguments here List> coercedArgs; if (auto toVecType = toType->As()) { auto toElementCount = toVecType->elementCount; auto toElementType = toVecType->elementType; UInt elementCount = 0; if (auto constElementCount = toElementCount.As()) { elementCount = (UInt) constElementCount->value; } else { // We don't know the element count statically, // so what are we supposed to be doing? elementCount = fromInitializerListExpr->args.Count(); } // TODO: need to check that the element count // for the vector type matches the argument // count for the initializer list, or else // fix them up to match. for(auto arg : fromInitializerListExpr->args) { RefPtr coercedArg; ConversionCost argCost; bool argResult = TryCoerceImpl( toElementType, outToExpr ? &coercedArg : nullptr, arg->type, arg, outCost ? &argCost : nullptr); // No point in trying further if any argument fails if(!argResult) return false; // TODO(tfoley): what to do with cost? // This only matters if/when we allow an initializer list as an argument to // an overloaded call. if( outToExpr ) { coercedArgs.Add(coercedArg); } } } // // TODO(tfoley): How to handle matrices here? // Should they expect individual scalars, or support // vectors for the rows? // if(auto toDeclRefType = toType->As()) { auto toTypeDeclRef = toDeclRefType->declRef; if(auto toStructDeclRef = toTypeDeclRef.As()) { // Trying to initialize a `struct` type given an initializer list. // We will go through the fields in order and try to match them // up with initializer arguments. UInt argIndex = 0; for(auto fieldDeclRef : getMembersOfType(toStructDeclRef)) { if(argIndex >= argCount) { // We've consumed all the arguments, so we should stop break; } auto arg = fromInitializerListExpr->args[argIndex++]; // RefPtr coercedArg; ConversionCost argCost; bool argResult = TryCoerceImpl( GetType(fieldDeclRef), outToExpr ? &coercedArg : nullptr, arg->type, arg, outCost ? &argCost : nullptr); // No point in trying further if any argument fails if(!argResult) return false; // TODO(tfoley): what to do with cost? // This only matters if/when we allow an initializer list as an argument to // an overloaded call. if( outToExpr ) { coercedArgs.Add(coercedArg); } } } } else if(auto toArrayType = toType->As()) { // TODO(tfoley): If we can compute the size of the array statically, // then we want to check that there aren't too many initializers present auto toElementType = toArrayType->baseType; for(auto& arg : fromInitializerListExpr->args) { RefPtr coercedArg; ConversionCost argCost; bool argResult = TryCoerceImpl( toElementType, outToExpr ? &coercedArg : nullptr, arg->type, arg, outCost ? &argCost : nullptr); // No point in trying further if any argument fails if(!argResult) return false; if( outToExpr ) { coercedArgs.Add(coercedArg); } } } else { // By default, we don't allow a type to be initialized using // an initializer list. return false; } // For now, coercion from an initializer list has no cost if(outCost) { *outCost = kConversionCost_None; } // We were able to coerce all the arguments given, and so // we need to construct a suitable expression to remember the result if(outToExpr) { auto toInitializerListExpr = new InitializerListExpr(); toInitializerListExpr->loc = fromInitializerListExpr->loc; toInitializerListExpr->type = QualType(toType); toInitializerListExpr->args = coercedArgs; *outToExpr = toInitializerListExpr; } return true; } // if (auto toDeclRefType = toType->As()) { auto toTypeDeclRef = toDeclRefType->declRef; if (auto interfaceDeclRef = toTypeDeclRef.As()) { // Trying to convert to an interface type. // // We will allow this if the type conforms to the interface. if (DoesTypeConformToInterface(fromType, interfaceDeclRef)) { if (outToExpr) *outToExpr = CreateImplicitCastExpr(toType, fromExpr); if (outCost) *outCost = kConversionCost_CastToInterface; return true; } } else if (auto genParamDeclRef = toTypeDeclRef.As()) { // We need to enumerate the constraints placed on this type by its outer // generic declaration, and see if any of them guarantees that we // satisfy the given interface.. auto genericDeclRef = genParamDeclRef.GetParent().As(); SLANG_ASSERT(genericDeclRef); for (auto constraintDeclRef : getMembersOfType(genericDeclRef)) { auto sub = GetSub(constraintDeclRef); auto sup = GetSup(constraintDeclRef); auto subDeclRef = sub->As(); if (!subDeclRef) continue; if (subDeclRef->declRef != genParamDeclRef) continue; auto supDeclRefType = sup->As(); if (supDeclRefType) { auto toInterfaceDeclRef = supDeclRefType->declRef.As(); if (DoesTypeConformToInterface(fromType, toInterfaceDeclRef)) { if (outToExpr) *outToExpr = CreateImplicitCastExpr(toType, fromExpr); if (outCost) *outCost = kConversionCost_CastToInterface; return true; } } } } } // Are we converting from a parameter group type to its element type? if(auto fromParameterGroupType = fromType->As()) { auto fromElementType = fromParameterGroupType->getElementType(); // If we have, e.g., `ConstantBuffer` and we want to convert // to `B`, where conversion from `A` to `B` is possible, then // we will do so here. ConversionCost subCost = 0; if(CanCoerce(toType, fromElementType, &subCost)) { if(outCost) *outCost = subCost + kConversionCost_ImplicitDereference; if(outToExpr) { auto derefExpr = new DerefExpr(); derefExpr->base = fromExpr; derefExpr->type = QualType(fromElementType); return TryCoerceImpl( toType, outToExpr, fromElementType, derefExpr, nullptr); } return true; } } // Look for an initializer/constructor declaration in the target type, // which is marked as usable for implicit conversion, and which takes // the source type as an argument. OverloadResolveContext overloadContext; overloadContext.disallowNestedConversions = true; overloadContext.argCount = 1; overloadContext.argTypes = &fromType; overloadContext.originalExpr = nullptr; if(fromExpr) { overloadContext.loc = fromExpr->loc; overloadContext.funcLoc = fromExpr->loc; overloadContext.args = &fromExpr; } overloadContext.baseExpr = nullptr; overloadContext.mode = OverloadResolveContext::Mode::JustTrying; AddTypeOverloadCandidates(toType, overloadContext, toType); if(overloadContext.bestCandidates.Count() != 0) { // There were multiple candidates that were equally good. // First, we will check if these candidates are even applicable. // If they aren't, then they can't be used for conversion. if(overloadContext.bestCandidates[0].status != OverloadCandidate::Status::Appicable) return false; // If we reach this point, then we have multiple candidates which are // all equally applicable, which means we have an ambiguity. // If the user is just querying whether a conversion is possible, we // will tell them it is, because ambiguity should trigger an ambiguity // error, and not a "no conversion possible" error. // We will compute a nominal conversion cost as the minimum over // all the conversions available. ConversionCost cost = kConversionCost_GeneralConversion; for(auto candidate : overloadContext.bestCandidates) { ConversionCost candidateCost = getImplicitConversionCost( candidate.item.declRef.getDecl()); if(candidateCost < cost) cost = candidateCost; } if(outCost) *outCost = cost; if(outToExpr) { // The user is asking for us to actually perform the conversion, // so we need to generate an appropriate expression here. // YONGH: I am confused why we are not hitting this case before //throw "foo bar baz"; // YONGH: temporary work around, may need to create the actual // invocation expr to the constructor call *outToExpr = fromExpr; } return true; } else if(overloadContext.bestCandidate) { // There is a single best candidate for conversion. // It might not actually be usable, so let's check that first. if(overloadContext.bestCandidate->status != OverloadCandidate::Status::Appicable) return false; // Okay, it is applicable, and we just need to let the user // know about it, and optionally construct a call. // We need to extract the conversion cost from the candidate we found. ConversionCost cost = getImplicitConversionCost( overloadContext.bestCandidate->item.declRef.getDecl());; if(outCost) *outCost = cost; if(outToExpr) { // The logic here is a bit ugly, to deal with the fact that // `CompleteOverloadCandidate` will, left to its own devices, // construct a vanilla `InvokeExpr` to represent the call // to the initializer we found, while we *want* it to // create some variety of `ImplicitCastExpr`. // // Now, it just so happens that `CompleteOverloadCandidate` // will use the "original" expression if one is available, // so we'll create one and initialize it here. // We fill in the location and arguments, but not the // base expression (the callee), since that will come // from the selected overload candidate. // auto castExpr = createImplicitCastExpr(); castExpr->loc = fromExpr->loc; castExpr->Arguments.Add(fromExpr); // // Next we need to set our cast expression as the "original" // expression and then complete the overload process. // overloadContext.originalExpr = castExpr; *outToExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate); // // However, the above isn't *quite* enough, because // the process of completing the overload candidate // might overwrite the argument list that was passed // in to overload resolution, and in this case that // "argument list" was just a pointer to `fromExpr`. // // That means we need to clear the argument list and // reload it from `fromExpr` to make sure that we // got the arguments *after* any transformations // were applied. // For right now this probably doesn't matter, // because we don't allow nested implicit conversions, // but I'd rather play it safe. // castExpr->Arguments.Clear(); castExpr->Arguments.Add(fromExpr); } return true; } return false; } // Check whether a type coercion is possible bool CanCoerce( RefPtr toType, // the target type for conversion RefPtr fromType, // the source type for the conversion ConversionCost* outCost = 0) // (optional) a place to stuff the conversion cost { BasicTypeKey key1, key2; BasicTypeKeyPair cacheKey; bool shouldAddToCache = false; ConversionCost cost; TypeCheckingCache* typeCheckingCache = getSession()->getTypeCheckingCache(); if (key1.fromType(toType.Ptr()) && key2.fromType(fromType.Ptr())) { cacheKey.type1 = key1; cacheKey.type2 = key2; if (typeCheckingCache->conversionCostCache.TryGetValue(cacheKey, cost)) { if (outCost) *outCost = cost; return cost != kConversionCost_Impossible; } else shouldAddToCache = true; } bool rs = TryCoerceImpl( toType, nullptr, fromType, nullptr, &cost); if (outCost) *outCost = cost; if (shouldAddToCache) { if (!rs) cost = kConversionCost_Impossible; typeCheckingCache->conversionCostCache[cacheKey] = cost; } return rs; } RefPtr createImplicitCastExpr() { return new ImplicitCastExpr(); } RefPtr CreateImplicitCastExpr( RefPtr toType, RefPtr fromExpr) { RefPtr castExpr = createImplicitCastExpr(); auto typeType = new TypeType(); typeType->setSession(getSession()); typeType->type = toType; auto typeExpr = new SharedTypeExpr(); typeExpr->type.type = typeType; typeExpr->base.type = toType; castExpr->loc = fromExpr->loc; castExpr->FunctionExpr = typeExpr; castExpr->type = QualType(toType); castExpr->Arguments.Add(fromExpr); return castExpr; } // Perform type coercion, and emit errors if it isn't possible RefPtr Coerce( RefPtr toType, RefPtr fromExpr) { RefPtr expr; if (!TryCoerceImpl( toType, &expr, fromExpr->type.Ptr(), fromExpr.Ptr(), nullptr)) { getSink()->diagnose(fromExpr->loc, Diagnostics::typeMismatch, toType, fromExpr->type); // Note(tfoley): We don't call `CreateErrorExpr` here, because that would // clobber the type on `fromExpr`, and an invariant here is that coercion // really shouldn't *change* the expression that is passed in, but should // introduce new AST nodes to coerce its value to a different type... return CreateImplicitCastExpr( getSession()->getErrorType(), fromExpr); } return expr; } void CheckVarDeclCommon(RefPtr varDecl) { // Check the type, if one was given TypeExp type = CheckUsableType(varDecl->type); // TODO: Additional validation rules on types should go here, // but we need to deal with the fact that some cases might be // allowed in one context (e.g., an unsized array parameter) // but not in othters (e.g., an unsized array field in a struct). // Check the initializers, if one was given RefPtr initExpr = CheckTerm(varDecl->initExpr); // If a type was given, ... if (type.Ptr()) { // then coerce any initializer to the type if (initExpr) { initExpr = Coerce(type.Ptr(), initExpr); } } else { // TODO: infer a type from the initializers if (!initExpr) { getSink()->diagnose(varDecl, Diagnostics::unimplemented, "variable declaration with no type must have initializer"); } else { getSink()->diagnose(varDecl, Diagnostics::unimplemented, "type inference for variable declaration"); } } varDecl->type = type; varDecl->initExpr = initExpr; } // Fill in default substitutions for the 'subtype' part of a type constraint decl void CheckConstraintSubType(TypeExp & typeExp) { if (auto sharedTypeExpr = typeExp.exp.As()) { if (auto declRefType = sharedTypeExpr->base->AsDeclRefType()) { declRefType->declRef.substitutions = createDefaultSubstitutions(getSession(), declRefType->declRef.getDecl()); if (auto typetype = typeExp.exp->type.type.As()) typetype->type = declRefType; } } } void CheckGenericConstraintDecl(GenericTypeConstraintDecl* decl) { // TODO: are there any other validations we can do at this point? // // There probably needs to be a kind of "occurs check" to make // sure that the constraint actually applies to at least one // of the parameters of the generic. if (decl->checkState == DeclCheckState::Unchecked) { decl->checkState = getCheckedState(); CheckConstraintSubType(decl->sub); decl->sub = TranslateTypeNodeForced(decl->sub); decl->sup = TranslateTypeNodeForced(decl->sup); } } void checkDecl(Decl* decl) { EnsureDecl(decl, checkingPhase == CheckingPhase::Header ? DeclCheckState::CheckedHeader : DeclCheckState::Checked); } void checkGenericDeclHeader(GenericDecl* genericDecl) { if (genericDecl->IsChecked(DeclCheckState::CheckedHeader)) return; // check the parameters for (auto m : genericDecl->Members) { if (auto typeParam = m.As()) { typeParam->initType = CheckProperType(typeParam->initType); } else if (auto valParam = m.As()) { // TODO: some real checking here... CheckVarDeclCommon(valParam); } else if (auto constraint = m.As()) { CheckGenericConstraintDecl(constraint.Ptr()); } } genericDecl->SetCheckState(DeclCheckState::CheckedHeader); } void visitGenericDecl(GenericDecl* genericDecl) { checkGenericDeclHeader(genericDecl); // check the nested declaration // TODO: this needs to be done in an appropriate environment... checkDecl(genericDecl->inner); genericDecl->SetCheckState(getCheckedState()); } void visitGenericTypeConstraintDecl(GenericTypeConstraintDecl * genericConstraintDecl) { if (genericConstraintDecl->IsChecked(DeclCheckState::CheckedHeader)) return; // check the type being inherited from auto base = genericConstraintDecl->sup; base = TranslateTypeNode(base); genericConstraintDecl->sup = base; } void visitInheritanceDecl(InheritanceDecl* inheritanceDecl) { if (inheritanceDecl->IsChecked(DeclCheckState::CheckedHeader)) return; // check the type being inherited from auto base = inheritanceDecl->base; CheckConstraintSubType(base); base = TranslateTypeNode(base); inheritanceDecl->base = base; // For now we only allow inheritance from interfaces, so // we will validate that the type expression names an interface if(auto declRefType = base.type->As()) { if(auto interfaceDeclRef = declRefType->declRef.As()) { return; } } // If type expression didn't name an interface, we'll emit an error here // TODO: deal with the case of an error in the type expression (don't cascade) getSink()->diagnose( base.exp, Diagnostics::expectedAnInterfaceGot, base.type); } RefPtr checkConstantIntVal( RefPtr expr) { // First type-check the expression as normal expr = CheckExpr(expr); auto intVal = CheckIntegerConstantExpression(expr.Ptr()); if(!intVal) return nullptr; auto constIntVal = intVal.As(); if(!constIntVal) { getSink()->diagnose(expr->loc, Diagnostics::expectedIntegerConstantNotLiteral); return nullptr; } return constIntVal; } // Check an expression, coerce it to the `String` type, and then // ensure that it has a literal (not just compile-time constant) value. bool checkLiteralStringVal( RefPtr expr, String* outVal) { // TODO: This should actually perform semantic checking, etc., // but for now we are just going to look for a direct string // literal AST node. if(auto stringLitExpr = expr.As()) { if(outVal) { *outVal = stringLitExpr->value; } return true; } getSink()->diagnose(expr, Diagnostics::expectedAStringLiteral); return false; } void visitSyntaxDecl(SyntaxDecl*) { // These are only used in the stdlib, so no checking is needed } void visitAttributeDecl(AttributeDecl*) { // These are only used in the stdlib, so no checking is needed } void visitGenericTypeParamDecl(GenericTypeParamDecl*) { // These are only used in the stdlib, so no checking is needed for now } void visitGenericValueParamDecl(GenericValueParamDecl*) { // These are only used in the stdlib, so no checking is needed for now } void visitModifier(Modifier*) { // Do nothing with modifiers for now } AttributeDecl* lookUpAttributeDecl(Name* attributeName, Scope* scope) { // Look up the name and see what we find. // // TODO: This needs to have some special filtering or naming // rules to keep us from seeing shadowing variable declarations. auto lookupResult = lookUp(getSession(), this, attributeName, scope, LookupMask::Attribute); // If we didn't find anything, or the result was overloaded, // then we aren't going to be able to extract a single decl. if(!lookupResult.isValid() || lookupResult.isOverloaded()) return nullptr; auto decl = lookupResult.item.declRef.getDecl(); if( auto attributeDecl = dynamic_cast(decl) ) { return attributeDecl; } else { return nullptr; } } Stage findStageByName(String const& name) { static const struct { char const* name; Stage stage; } kStages[] = { #define PROFILE_STAGE(ID, NAME, ENUM) \ { #NAME, Stage::ID }, #define PROFILE_STAGE_ALIAS(ID, NAME, VAL) \ { #NAME, Stage::ID }, #include "profile-defs.h" }; for(auto entry : kStages) { if(name == entry.name) { return entry.stage; } } return Stage::Unknown; } bool validateAttribute(RefPtr attr) { if(auto numThreadsAttr = attr.As()) { SLANG_ASSERT(attr->args.Count() == 3); auto xVal = checkConstantIntVal(attr->args[0]); auto yVal = checkConstantIntVal(attr->args[1]); auto zVal = checkConstantIntVal(attr->args[2]); if(!xVal) return false; if(!yVal) return false; if(!zVal) return false; numThreadsAttr->x = (int32_t) xVal->value; numThreadsAttr->y = (int32_t) yVal->value; numThreadsAttr->z = (int32_t) zVal->value; } else if (auto maxVertexCountAttr = attr.As()) { SLANG_ASSERT(attr->args.Count() == 1); auto val = checkConstantIntVal(attr->args[0]); if(!val) return false; maxVertexCountAttr->value = (int32_t)val->value; } else if(auto instanceAttr = attr.As()) { SLANG_ASSERT(attr->args.Count() == 1); auto val = checkConstantIntVal(attr->args[0]); if(!val) return false; instanceAttr->value = (int32_t)val->value; } else if(auto entryPointAttr = attr.As()) { SLANG_ASSERT(attr->args.Count() == 1); String stageName; if(!checkLiteralStringVal(attr->args[0], &stageName)) { return false; } auto stage = findStageByName(stageName); if(stage == Stage::Unknown) { getSink()->diagnose(attr->args[0], Diagnostics::unknownStageName, stageName); } entryPointAttr->stage = stage; } else { if(attr->args.Count() == 0) { // If the attribute took no arguments, then we will // assume it is valid as written. } else { // We should be special-casing the checking of any attribute // with a non-zero number of arguments. SLANG_DIAGNOSE_UNEXPECTED(getSink(), attr, "unhandled attribute"); return false; } } return true; } RefPtr checkAttribute( UncheckedAttribute* uncheckedAttr, ModifiableSyntaxNode* attrTarget) { auto attrName = uncheckedAttr->getName(); auto attrDecl = lookUpAttributeDecl( attrName, uncheckedAttr->scope); if(!attrDecl) { getSink()->diagnose(uncheckedAttr, Diagnostics::unknownAttributeName, attrName); return uncheckedAttr; } if(!attrDecl->syntaxClass.isSubClassOf()) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), attrDecl, "attribute declaration does not reference an attribute class"); return uncheckedAttr; } RefPtr attrObj = attrDecl->syntaxClass.createInstance(); auto attr = attrObj.As(); if(!attr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), attrDecl, "attribute class did not yield an attribute object"); return uncheckedAttr; } // We are going to replace the unchecked attribute with the checked one. // First copy all of the state over from the original attribute. attr->name = uncheckedAttr->name; attr->args = uncheckedAttr->args; attr->loc = uncheckedAttr->loc; // We will start with checking steps that can be applied independent // of the concrete attribute type that was selected. These only need // us to look at the attribute declaration itself. // // Start by doing argument/parameter matching UInt argCount = attr->args.Count(); UInt paramCounter = 0; bool mismatch = false; for(auto paramDecl : attrDecl->getMembersOfType()) { UInt paramIndex = paramCounter++; if( paramIndex < argCount ) { auto arg = attr->args[paramIndex]; // TODO: support checking the argument against the declared // type for the parameter. } else { // We didn't have enough arguments for the // number of parameters declared. if(auto defaultArg = paramDecl->initExpr) { // The attribute declaration provided a default, // so we should use that. // // TODO: we need to figure out how to hook up // default arguments as needed. } else { mismatch = true; } } } UInt paramCount = paramCounter; if(mismatch) { getSink()->diagnose(attr, Diagnostics::attributeArgumentCountMismatch, attrName, paramCount, argCount); return uncheckedAttr; } // The next bit of validation that we can apply semi-generically // is to validate that the target for this attribute is a valid // one for the chosen attribute. // // The attribute declaration will have one or more `AttributeTargetModifier`s // that each specify a syntax class that the attribute can be applied to. // If any of these match `attrTarget`, then we are good. // bool validTarget = false; for(auto attrTargetMod : attrDecl->GetModifiersOfType()) { if(attrTarget->getClass().isSubClassOf(attrTargetMod->syntaxClass)) { validTarget = true; break; } } if(!validTarget) { getSink()->diagnose(attr, Diagnostics::attributeNotApplicable, attrName); return uncheckedAttr; } // Now apply type-specific validation to the attribute. if(!validateAttribute(attr)) { return uncheckedAttr; } return attr; } RefPtr checkModifier( RefPtr m, ModifiableSyntaxNode* syntaxNode) { if(auto hlslUncheckedAttribute = m.As()) { // We have an HLSL `[name(arg,...)]` attribute, and we'd like // to check that it is provides all the expected arguments // // First, look up the attribute name in the current scope to find // the right syntax class to instantiate. // return checkAttribute(hlslUncheckedAttribute, syntaxNode); } // Default behavior is to leave things as they are, // and assume that modifiers are mostly already checked. // // TODO: This would be a good place to validate that // a modifier is actually valid for the thing it is // being applied to, and potentially to check that // it isn't in conflict with any other modifiers // on the same declaration. return m; } void checkModifiers(ModifiableSyntaxNode* syntaxNode) { // TODO(tfoley): need to make sure this only // performs semantic checks on a `SharedModifier` once... // The process of checking a modifier may produce a new modifier in its place, // so we will build up a new linked list of modifiers that will replace // the old list. RefPtr resultModifiers; RefPtr* resultModifierLink = &resultModifiers; RefPtr modifier = syntaxNode->modifiers.first; while(modifier) { // Because we are rewriting the list in place, we need to extract // the next modifier here (not at the end of the loop). auto next = modifier->next; // We also go ahead and clobber the `next` field on the modifier // itself, so that the default behavior of `checkModifier()` can // be to return a single unlinked modifier. modifier->next = nullptr; auto checkedModifier = checkModifier(modifier, syntaxNode); if(checkedModifier) { // If checking gave us a modifier to add, then we // had better add it. // Just in case `checkModifier` ever returns multiple // modifiers, lets advance to the end of the list we // are building. while(*resultModifierLink) resultModifierLink = &(*resultModifierLink)->next; // attach the new modifier at the end of the list, // and now set the "link" to point to its `next` field *resultModifierLink = checkedModifier; resultModifierLink = &checkedModifier->next; } // Move along to the next modifier modifier = next; } // Whether we actually re-wrote anything or note, lets // install the new list of modifiers on the declaration syntaxNode->modifiers.first = resultModifiers; } void visitModuleDecl(ModuleDecl* programNode) { // Try to register all the builtin decls for (auto decl : programNode->Members) { auto inner = decl; if (auto genericDecl = decl.As()) { inner = genericDecl->inner; } if (auto builtinMod = inner->FindModifier()) { registerBuiltinDecl(getSession(), decl, builtinMod); } if (auto magicMod = inner->FindModifier()) { registerMagicDecl(getSession(), decl, magicMod); } } // We need/want to visit any `import` declarations before // anything else, to make sure that scoping works. for(auto& importDecl : programNode->getMembersOfType()) { checkDecl(importDecl); } // register all extensions for (auto & s : programNode->getMembersOfType()) registerExtension(s); for (auto & g : programNode->getMembersOfType()) { if (auto extDecl = g->inner->As()) { checkGenericDeclHeader(g); registerExtension(extDecl); } } // check types for (auto & s : programNode->getMembersOfType()) checkDecl(s.Ptr()); for (int pass = 0; pass < 2; pass++) { checkingPhase = pass == 0 ? CheckingPhase::Header : CheckingPhase::Body; for (auto & s : programNode->getMembersOfType()) { checkDecl(s.Ptr()); } // HACK(tfoley): Visiting all generic declarations here, // because otherwise they won't get visited. for (auto & g : programNode->getMembersOfType()) { checkDecl(g.Ptr()); } // before checking conformance, make sure we check all the extension bodies // generic extension decls are already checked by the loop above for (auto & s : programNode->getMembersOfType()) checkDecl(s); for (auto & func : programNode->getMembersOfType()) { if (!func->IsChecked(getCheckedState())) { VisitFunctionDeclaration(func.Ptr()); } } for (auto & func : programNode->getMembersOfType()) { checkDecl(func); } if (sink->GetErrorCount() != 0) return; // Force everything to be fully checked, just in case // Note that we don't just call this on the program, // because we'd end up recursing into this very code path... for (auto d : programNode->Members) { EnusreAllDeclsRec(d); } // Do any semantic checking required on modifiers? for (auto d : programNode->Members) { checkModifiers(d.Ptr()); } if (pass == 0) { // now we can check all interface conformances for (auto & s : programNode->getMembersOfType()) checkAggTypeConformance(s); for (auto & s : programNode->getMembersOfType()) checkExtensionConformance(s); for (auto & g : programNode->getMembersOfType()) { if (auto innerAggDecl = g->inner->As()) checkAggTypeConformance(innerAggDecl); else if (auto innerExtDecl = g->inner->As()) checkExtensionConformance(innerExtDecl); } } } } void visitStructField(StructField* field) { if (field->IsChecked(DeclCheckState::CheckedHeader)) return; // TODO: bottleneck through general-case variable checking field->type = CheckUsableType(field->type); field->SetCheckState(getCheckedState()); } bool doesSignatureMatchRequirement( DeclRef satisfyingMemberDeclRef, DeclRef requiredMemberDeclRef, RefPtr witnessTable) { // TODO: actually implement matching here. For now we'll // just pretend that things are satisfied in order to make progress.. witnessTable->requirementDictionary.Add( requiredMemberDeclRef.getDecl(), RequirementWitness(satisfyingMemberDeclRef)); return true; } bool doesGenericSignatureMatchRequirement( DeclRef genDecl, DeclRef requirementGenDecl, RefPtr witnessTable) { if (genDecl.getDecl()->Members.Count() != requirementGenDecl.getDecl()->Members.Count()) return false; for (UInt i = 0; i < genDecl.getDecl()->Members.Count(); i++) { auto genMbr = genDecl.getDecl()->Members[i]; auto requiredGenMbr = genDecl.getDecl()->Members[i]; if (auto genTypeMbr = genMbr.As()) { if (auto requiredGenTypeMbr = requiredGenMbr.As()) { } else return false; } else if (auto genValMbr = genMbr.As()) { if (auto requiredGenValMbr = requiredGenMbr.As()) { if (!genValMbr->type->Equals(requiredGenValMbr->type)) return false; } else return false; } else if (auto genTypeConstraintMbr = genMbr.As()) { if (auto requiredTypeConstraintMbr = requiredGenMbr.As()) { if (!genTypeConstraintMbr->sup->Equals(requiredTypeConstraintMbr->sup)) { return false; } } else return false; } } // TODO: this isn't right, because we need to specialize the // declarations of the generics to a common set of substitutions, // so that their types are comparable (e.g., foo and foo // need to have substutition applies so that they are both foo, // after which uses of the type X in their parameter lists can // be compared). return doesMemberSatisfyRequirement( DeclRef(genDecl.getDecl()->inner.Ptr(), genDecl.substitutions), DeclRef(requirementGenDecl.getDecl()->inner.Ptr(), requirementGenDecl.substitutions), witnessTable); } bool doesTypeSatisfyAssociatedTypeRequirement( RefPtr satisfyingType, DeclRef requiredAssociatedTypeDeclRef, RefPtr witnessTable) { // We need to confirm that the chosen type `satisfyingType`, // meets all the constraints placed on the associated type // requirement `requiredAssociatedTypeDeclRef`. // // We will enumerate the type constraints placed on the // associated type and see if they can be satisfied. // bool conformance = true; for (auto requiredConstraintDeclRef : getMembersOfType(requiredAssociatedTypeDeclRef)) { // Grab the type we expect to conform to from the constraint. auto requiredSuperType = GetSup(requiredConstraintDeclRef); // Perform a search for a witness to the subtype relationship. auto witness = tryGetSubtypeWitness(satisfyingType, requiredSuperType); if(witness) { // If a subtype witness was found, then the conformance // appears to hold, and we can satisfy that requirement. witnessTable->requirementDictionary.Add(requiredConstraintDeclRef, RequirementWitness(witness)); } else { // If a witness couldn't be found, then the conformance // seems like it will fail. conformance = false; } } // TODO: if any conformance check failed, we should probably include // that in an error message produced about not satisfying the requirement. if(conformance) { // If all the constraints were satsified, then the chosen // type can indeed satisfy the interface requirement. witnessTable->requirementDictionary.Add( requiredAssociatedTypeDeclRef.getDecl(), RequirementWitness(satisfyingType)); } return conformance; } // Does the given `memberDecl` work as an implementation // to satisfy the requirement `requiredMemberDeclRef` // from an interface? // // If it does, then inserts a witness into `witnessTable` // and returns `true`, otherwise returns `false` bool doesMemberSatisfyRequirement( DeclRef memberDeclRef, DeclRef requiredMemberDeclRef, RefPtr witnessTable) { // At a high level, we want to check that the // `memberDecl` and the `requiredMemberDeclRef` // have the same AST node class, and then also // check that their signatures match. // // There are a bunch of detailed decisions that // have to be made, though, because we might, e.g., // allow a function with more general parameter // types to satisfy a requirement with more // specific parameter types. // // If we ever allow for "property" declarations, // then we would probably need to allow an // ordinary field to satisfy a property requirement. // // An associated type requirement should be allowed // to be satisfied by any type declaration: // a typedef, a `struct`, etc. // if (auto memberFuncDecl = memberDeclRef.As()) { if (auto requiredFuncDeclRef = requiredMemberDeclRef.As()) { // Check signature match. return doesSignatureMatchRequirement( memberFuncDecl, requiredFuncDeclRef, witnessTable); } } else if (auto memberInitDecl = memberDeclRef.As()) { if (auto requiredInitDecl = requiredMemberDeclRef.As()) { // Check signature match. return doesSignatureMatchRequirement( memberInitDecl, requiredInitDecl, witnessTable); } } else if (auto genDecl = memberDeclRef.As()) { // For a generic member, we will check if it can satisfy // a generic requirement in the interface. // // TODO: we could also conceivably check that the generic // could be *specialized* to satisfy the requirement, // and then install a specialization of the generic into // the witness table. Actually doing this would seem // to require performing something akin to overload // resolution as part of requirement satisfaction. // if (auto requiredGenDeclRef = requiredMemberDeclRef.As()) { return doesGenericSignatureMatchRequirement(genDecl, requiredGenDeclRef, witnessTable); } } else if (auto subAggTypeDeclRef = memberDeclRef.As()) { if(auto requiredTypeDeclRef = requiredMemberDeclRef.As()) { checkDecl(subAggTypeDeclRef.getDecl()); auto satisfyingType = DeclRefType::Create(getSession(), subAggTypeDeclRef); return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable); } } else if (auto typedefDeclRef = memberDeclRef.As()) { // this is a type-def decl in an aggregate type // check if the specified type satisfies the constraints defined by the associated type if (auto requiredTypeDeclRef = requiredMemberDeclRef.As()) { checkDecl(typedefDeclRef.getDecl()); auto satisfyingType = getNamedType(getSession(), typedefDeclRef); return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable); } } // Default: just assume that thing aren't being satisfied. return false; } // State used while checking if a declaration (either a type declaration // or an extension of that type) conforms to the interfaces it claims // via its inheritance clauses. // struct ConformanceCheckingContext { Dictionary, RefPtr> mapInterfaceToWitnessTable; }; // Find the appropriate member of a declared type to // satisfy a requirement of an interface the type // claims to conform to. // // The type declaration `typeDecl` has declared that it // conforms to the interface `interfaceDeclRef`, and // `requiredMemberDeclRef` is a required member of // the interface. // // If a satisfying value is found, registers it in // `witnessTable` and returns `true`, otherwise // returns `false`. // bool findWitnessForInterfaceRequirement( ConformanceCheckingContext* context, DeclRef typeDeclRef, InheritanceDecl* inheritanceDecl, DeclRef interfaceDeclRef, DeclRef requiredMemberDeclRef, RefPtr witnessTable) { // The goal of this function is to find a suitable // value to satisfy the requirement. // // The 99% case is that the requirement is a named member // of the interface, and we need to search for a member // with the same name in the type declaration and // its (known) extensions. // As a first pass, lets check if we already have a // witness in the table for the requirement, so // that we can bail out early. // if(witnessTable->requirementDictionary.ContainsKey(requiredMemberDeclRef.getDecl())) { return true; } // An important exception to the above is that an // inheritance declaration in the interface is not going // to be satisfied by an inheritance declaration in the // conforming type, but rather by a full "witness table" // full of the satisfying values for each requirement // in the inherited-from interface. // if( auto requiredInheritanceDeclRef = requiredMemberDeclRef.As() ) { // Recursively check that the type conforms // to the inherited interface. // // TODO: we *really* need a linearization step here!!!! RefPtr satisfyingWitnessTable = checkConformanceToType( context, typeDeclRef, requiredInheritanceDeclRef.getDecl(), getBaseType(requiredInheritanceDeclRef)); if(!satisfyingWitnessTable) return false; witnessTable->requirementDictionary.Add( requiredInheritanceDeclRef.getDecl(), RequirementWitness(satisfyingWitnessTable)); return true; } // We will look up members with the same name, // since only same-name members will be able to // satisfy the requirement. // // TODO: this won't work right now for members that // don't have names, which right now includes // initializers/constructors. Name* name = requiredMemberDeclRef.GetName(); // We are basically looking up members of the // given type, but we need to be a bit careful. // We *cannot* perfom lookup "through" inheritance // declarations for this or other interfaces, // since that would let us satisfy a requirement // with itself. // // There's also an interesting question of whether // we can/should support innterface requirements // being satisfied via `__transparent` members. // This seems like a "clever" idea rather than // a useful one, and IR generation would // need to construct real IR to trampoline over // to the implementation. // // The final case that can't be reduced to just // "a directly declared member with the same name" // is the case where the type inherits a member // that can satisfy the requirement from a base type. // We are ignoring implementation inheritance for // now, so we won't worry about this. // Make sure that by-name lookup is possible. buildMemberDictionary(typeDeclRef.getDecl()); auto lookupResult = lookUpLocal(getSession(), this, name, typeDeclRef); if (!lookupResult.isValid()) { getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDeclRef, requiredMemberDeclRef); return false; } // Iterate over the members and look for one that matches // the expected signature for the requirement. for (auto member : lookupResult) { if (doesMemberSatisfyRequirement(member.declRef, requiredMemberDeclRef, witnessTable)) return true; } // No suitable member found, although there were candidates. // // TODO: Eventually we might want something akin to the current // overload resolution logic, where we keep track of a list // of "candidates" for satisfaction of the requirement, // and if nothing is found we print the candidates getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDeclRef, requiredMemberDeclRef); return false; } // Check that the type declaration `typeDecl`, which // declares conformance to the interface `interfaceDeclRef`, // (via the given `inheritanceDecl`) actually provides // members to satisfy all the requirements in the interface. RefPtr checkInterfaceConformance( ConformanceCheckingContext* context, DeclRef typeDeclRef, InheritanceDecl* inheritanceDecl, DeclRef interfaceDeclRef) { // Has somebody already checked this conformance, // and/or is in the middle of checking it? RefPtr witnessTable; if(context->mapInterfaceToWitnessTable.TryGetValue(interfaceDeclRef, witnessTable)) return witnessTable; // We need to check the declaration of the interface // before we can check that we conform to it. checkDecl(interfaceDeclRef.getDecl()); // We will construct the witness table, and register it // *before* we go about checking fine-grained requirements, // in order to short-circuit any potential for infinite recursion. // Note: we will re-use the witnes table attached to the inheritance decl, // if there is one. This catches cases where semantic checking might // have synthesized some of the conformance witnesses for us. // witnessTable = inheritanceDecl->witnessTable; if(!witnessTable) { witnessTable = new WitnessTable(); } context->mapInterfaceToWitnessTable.Add(interfaceDeclRef, witnessTable); bool result = true; // TODO: If we ever allow for implementation inheritance, // then we will need to consider the case where a type // declares that it conforms to an interface, but one of // its (non-interface) base types already conforms to // that interface, so that all of the requirements are // already satisfied with inherited implementations... for(auto requiredMemberDeclRef : getMembers(interfaceDeclRef)) { auto requirementSatisfied = findWitnessForInterfaceRequirement( context, typeDeclRef, inheritanceDecl, interfaceDeclRef, requiredMemberDeclRef, witnessTable); result = result && requirementSatisfied; } // Extensions that apply to the interface type can create new conformances // for the concrete types that inherit from the interface. // // These new conformances should not be able to introduce new *requirements* // for an implementing interface (although they currently can), but we // still need to go through this logic to find the appropriate value // that will satisfy the requirement in these cases, and also to put // the required entry into the witness table for the interface itself. // // TODO: This logic is a bit slippery, and we need to figure out what // it means in the context of separate compilation. If module A defines // an interface IA, module B defines a type C that conforms to IA, and then // module C defines an extension that makes IA conform to IC, then it is // unreasonable to expect the {B:IA} witness table to contain an entry // corresponding to {IA:IC}. // // The simple answer then would be that the {IA:IC} conformance should be // fixed, with a single witness table for {IA:IC}, but then what should // happen in B explicitly conformed to IC already? // // For now we will just walk through the extensions that are known at // the time we are compiling and handle those, and punt on the larger issue // for abit longer. for(auto candidateExt = interfaceDeclRef.getDecl()->candidateExtensions; candidateExt; candidateExt = candidateExt->nextCandidateExtension) { // We need to apply the extension to the interface type that our // concrete type is inheriting from. // // TODO: need to decide if a this-type substitution is needed here. // It probably it. RefPtr targetType = DeclRefType::Create( getSession(), interfaceDeclRef); auto extDeclRef = ApplyExtensionToType(candidateExt, targetType); if(!extDeclRef) continue; // Only inheritance clauses from the extension matter right now. for(auto requiredInheritanceDeclRef : getMembersOfType(extDeclRef)) { auto requirementSatisfied = findWitnessForInterfaceRequirement( context, typeDeclRef, inheritanceDecl, interfaceDeclRef, requiredInheritanceDeclRef, witnessTable); result = result && requirementSatisfied; } } // If we failed to satisfy any requirements along the way, // then we don't actually want to keep the witness table // we've been constructing, because the whole thing was a failure. if(!result) { return nullptr; } return witnessTable; } RefPtr checkConformanceToType( ConformanceCheckingContext* context, DeclRef typeDeclRef, InheritanceDecl* inheritanceDecl, Type* baseType) { if (auto baseDeclRefType = baseType->As()) { auto baseTypeDeclRef = baseDeclRefType->declRef; if (auto baseInterfaceDeclRef = baseTypeDeclRef.As()) { // The type is stating that it conforms to an interface. // We need to check that it provides all of the members // required by that interface. return checkInterfaceConformance( context, typeDeclRef, inheritanceDecl, baseInterfaceDeclRef); } } getSink()->diagnose(inheritanceDecl, Diagnostics::unimplemented, "type not supported for inheritance"); return nullptr; } // Check that the type (or extension) declaration `declRef`, // which declares that it inherits from another type via // `inheritanceDecl` actually does what it needs to // for that inheritance to be valid. bool checkConformance( DeclRef declRef, InheritanceDecl* inheritanceDecl) { declRef = createDefaultSubstitutionsIfNeeded(getSession(), declRef).As(); // Don't check conformances for abstract types that // are being used to express *required* conformances. if (auto assocTypeDeclRef = declRef.As()) { // An associated type declaration represents a requirement // in an outer interface declaration, and its members // (type constraints) represent additional requirements. return true; } else if (auto interfaceDeclRef = declRef.As()) { // HACK: Our semantics as they stand today are that an // `extension` of an interface that adds a new inheritance // clause acts *as if* that inheritnace clause had been // attached to the original `interface` decl: that is, // it adds additional requirements. // // This is *not* a reasonable semantic to keep long-term, // but it is required for some of our current example // code to work. return true; } // Look at the type being inherited from, and validate // appropriately. auto baseType = inheritanceDecl->base.type; ConformanceCheckingContext context; RefPtr witnessTable = checkConformanceToType(&context, declRef, inheritanceDecl, baseType); if(!witnessTable) return false; inheritanceDecl->witnessTable = witnessTable; return true; } void checkExtensionConformance(ExtensionDecl* decl) { if (auto targetDeclRefType = decl->targetType->As()) { if (auto aggTypeDeclRef = targetDeclRefType->declRef.As()) { for (auto inheritanceDecl : decl->getMembersOfType()) { checkConformance(aggTypeDeclRef, inheritanceDecl); } } } } void checkAggTypeConformance(AggTypeDecl* decl) { // After we've checked members, we need to go through // any inheritance clauses on the type itself, and // confirm that the type actually provides whatever // those clauses require. if (auto interfaceDecl = dynamic_cast(decl)) { // Don't check that an interface conforms to the // things it inherits from. } else if (auto assocTypeDecl = dynamic_cast(decl)) { // Don't check that an associated type decl conforms to the // things it inherits from. } else { // For non-interface types we need to check conformance. // // TODO: Need to figure out what this should do for // `abstract` types if we ever add them. Should they // be required to implement all interface requirements, // just with `abstract` methods that replicate things? // (That's what C# does). for (auto inheritanceDecl : decl->getMembersOfType()) { checkConformance(makeDeclRef(decl), inheritanceDecl); } } } void visitAggTypeDecl(AggTypeDecl* decl) { if (decl->IsChecked(getCheckedState())) return; // TODO: we should check inheritance declarations // first, since they need to be validated before // we can make use of the type (e.g., you need // to know that `A` inherits from `B` in order // to check an expression like `aValue.bMethod()` // where `aValue` is of type `A` but `bMethod` // is defined in type `B`. // // TODO: We should also add a pass that takes // all the stated inheritance relationships, // expands them to include implicitic inheritance, // and then linearizes them. This would allow // later passes that need to know everything // a type inherits from to proceed linearly // through the list, rather than having to // recurse (and potentially see the same interface // more than once). decl->SetCheckState(DeclCheckState::CheckedHeader); // Now check all of the member declarations. for (auto member : decl->Members) { checkDecl(member); } decl->SetCheckState(getCheckedState()); } // Validate that `type` is a suitable type to use // as the tag type for an `enum` void validateEnumTagType(Type* type, SourceLoc const& loc) { if(auto basicType = type->As()) { switch(basicType->baseType) { default: // By default, don't allow a type to be used // as an `enum` tag type. break; case BaseType::Int: case BaseType::UInt: case BaseType::UInt64: // These are all allowed. return; } } getSink()->diagnose(loc, Diagnostics::invalidEnumTagType, type); } void visitEnumDecl(EnumDecl* decl) { if (decl->IsChecked(getCheckedState())) return; // Look at inheritance clauses, and // see if one of them is making the enum // "inherit" from a concrete type. // This will become the "tag" type // of the enum. RefPtr tagType; InheritanceDecl* tagTypeInheritanceDecl = nullptr; for(auto inheritanceDecl : decl->getMembersOfType()) { checkDecl(inheritanceDecl); // Look at the type being inherited from. auto superType = inheritanceDecl->base.type; if(auto errorType = superType->As()) { // Ignore any erroneous inheritance clauses. continue; } else if(auto declRefType = superType->As()) { if(auto interfaceDeclRef = declRefType->declRef.As()) { // Don't consider interface bases as candidates for // the tag type. continue; } } if(tagType) { // We already found a tag type. getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType); getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType); break; } else { tagType = superType; tagTypeInheritanceDecl = inheritanceDecl; } } // If a tag type has not been set, then we // default it to the built-in `int` type. // // TODO: In the far-flung future we may want to distinguish // `enum` types that have a "raw representation" like this from // ones that are purely abstract and don't expose their // type of their tag. if(!tagType) { tagType = getSession()->getIntType(); } else { // TODO: Need to establish that the tag // type is suitable. (e.g., if we are going // to allow raw values for case tags to be // derived automatically, then the tag // type needs to be some kind of interer type...) // // For now we will just be harsh and require it // to be one of a few builtin types. validateEnumTagType(tagType, tagTypeInheritanceDecl->loc); } decl->tagType = tagType; // An `enum` type should automatically conform to the `__EnumType` interface. // The compiler needs to insert this conformance behind the scenes, and this // seems like the best place to do it. { // First, look up the type of the `__EnumType` interface. RefPtr enumTypeType = getSession()->getEnumTypeType(); RefPtr enumConformanceDecl = new InheritanceDecl(); enumConformanceDecl->ParentDecl = decl; enumConformanceDecl->loc = decl->loc; enumConformanceDecl->base.type = getSession()->getEnumTypeType(); decl->Members.Add(enumConformanceDecl); // The `__EnumType` interface has one required member, the `__Tag` type. // We need to satisfy this requirement automatically, rather than require // the user to actually declare a member with this name (otherwise we wouldn't // let them define a tag value with the name `__Tag`). // RefPtr witnessTable = new WitnessTable(); enumConformanceDecl->witnessTable = witnessTable; Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag"); Decl* tagAssociatedTypeDecl = nullptr; if(auto enumTypeTypeDeclRefType = enumTypeType.As()) { if(auto enumTypeTypeInterfaceDecl = enumTypeTypeDeclRefType->declRef.getDecl()->As()) { for(auto memberDecl : enumTypeTypeInterfaceDecl->Members) { if(memberDecl->getName() == tagAssociatedTypeName) { tagAssociatedTypeDecl = memberDecl; break; } } } } if(!tagAssociatedTypeDecl) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), decl, "failed to find built-in declaration '__Tag'"); } // Okay, add the conformance withess for `__Tag` being satisfied by `tagType` witnessTable->requirementDictionary.Add(tagAssociatedTypeDecl, RequirementWitness(tagType)); // TODO: we actually also need to synthesize a witness for the conformance of `tagType` // to the `__BuiltinIntegerType` interface, because that is a constraint on the // associated type `__Tag`. // TODO: eventually we should consider synthesizing other requirements for // the min/max tag values, or the total number of tags, so that people don't // have to declare these as additional cases. enumConformanceDecl->SetCheckState(DeclCheckState::Checked); } decl->SetCheckState(DeclCheckState::CheckedHeader); auto enumType = DeclRefType::Create( getSession(), makeDeclRef(decl)); // Check the enum cases in order. for(auto caseDecl : decl->getMembersOfType()) { // Each case defines a value of the enum's type. // // TODO: If we ever support enum cases with payloads, // then they would probably have a type that is a // `FunctionType` from the payload types to the // enum type. // caseDecl->type.type = enumType; checkDecl(caseDecl); } // For any enum case that didn't provide an explicit // tag value, derived an appropriate tag value. IntegerLiteralValue defaultTag = 0; for(auto caseDecl : decl->getMembersOfType()) { if(auto explicitTagValExpr = caseDecl->initExpr) { // This tag has an initializer, so it should establish // the tag value for a successor case that doesn't // provide an explicit tag. RefPtr explicitTagVal = TryConstantFoldExpr(explicitTagValExpr); if(explicitTagVal) { if(auto constIntVal = explicitTagVal.As()) { defaultTag = constIntVal->value; } else { // TODO: need to handle other possibilities here getSink()->diagnose(explicitTagValExpr, Diagnostics::unexpectedEnumTagExpr); } } else { // If this happens, then the explicit tag value expression // doesn't seem to be a constant after all. In this case // we expect the checking logic to have applied already. } } else { // This tag has no initializer, so it should use // the default tag value we are tracking. RefPtr tagValExpr = new IntegerLiteralExpr(); tagValExpr->loc = caseDecl->loc; tagValExpr->type = QualType(tagType); tagValExpr->value = defaultTag; caseDecl->initExpr = tagValExpr; } // Default tag for the next case will be one more than // for the most recent case. // // TODO: We might consider adding a `[flags]` attribute // that modifies this behavior to be `defaultTagForCase <<= 1`. // defaultTag++; } // Now check any other member declarations. for(auto memberDecl : decl->Members) { // Already checked inheritance declarations above. if(auto inheritanceDecl = memberDecl->As()) continue; // Already checked enum case declarations above. if(auto caseDecl = memberDecl->As()) continue; // TODO: Right now we don't support other kinds of // member declarations on an `enum`, but that is // something we may want to allow in the long run. // checkDecl(memberDecl); } decl->SetCheckState(getCheckedState()); } void visitEnumCaseDecl(EnumCaseDecl* decl) { if (decl->IsChecked(getCheckedState())) return; // An enum case had better appear inside an enum! // // TODO: Do we need/want to support generic cases some day? auto parentEnumDecl = decl->ParentDecl->As(); SLANG_ASSERT(parentEnumDecl); // The tag type should have already been set by // the surrounding `enum` declaration. auto tagType = parentEnumDecl->tagType; SLANG_ASSERT(tagType); // Need to check the init expression, if present, since // that represents the explicit tag for this case. if(auto initExpr = decl->initExpr) { initExpr = CheckExpr(initExpr); initExpr = Coerce(tagType, initExpr); // We want to enforce that this is an integer constant // expression, but we don't actually care to retain // the value. CheckIntegerConstantExpression(initExpr); decl->initExpr = initExpr; } decl->SetCheckState(getCheckedState()); } void visitDeclGroup(DeclGroup* declGroup) { for (auto decl : declGroup->decls) { dispatchDecl(decl); } } void visitTypeDefDecl(TypeDefDecl* decl) { if (decl->IsChecked(getCheckedState())) return; if (checkingPhase == CheckingPhase::Header) { decl->type = CheckProperType(decl->type); } decl->SetCheckState(getCheckedState()); } void visitGlobalGenericParamDecl(GlobalGenericParamDecl * decl) { if (decl->IsChecked(getCheckedState())) return; if (checkingPhase == CheckingPhase::Header) { decl->SetCheckState(DeclCheckState::CheckedHeader); // global generic param only allowed in global scope auto program = decl->ParentDecl->As(); if (!program) getSink()->diagnose(decl, Slang::Diagnostics::globalGenParamInGlobalScopeOnly); // Now check all of the member declarations. for (auto member : decl->Members) { checkDecl(member); } } decl->SetCheckState(getCheckedState()); } void visitAssocTypeDecl(AssocTypeDecl* decl) { if (decl->IsChecked(getCheckedState())) return; if (checkingPhase == CheckingPhase::Header) { decl->SetCheckState(DeclCheckState::CheckedHeader); // assoctype only allowed in an interface auto interfaceDecl = decl->ParentDecl->As(); if (!interfaceDecl) getSink()->diagnose(decl, Slang::Diagnostics::assocTypeInInterfaceOnly); // Now check all of the member declarations. for (auto member : decl->Members) { checkDecl(member); } } decl->SetCheckState(getCheckedState()); } void checkStmt(Stmt* stmt) { if (!stmt) return; dispatchStmt(stmt); checkModifiers(stmt); } void visitFuncDecl(FuncDecl *functionNode) { if (functionNode->IsChecked(getCheckedState())) return; if (checkingPhase == CheckingPhase::Header) { VisitFunctionDeclaration(functionNode); } // TODO: This should really only set "checked header" functionNode->SetCheckState(getCheckedState()); if (checkingPhase == CheckingPhase::Body) { // TODO: should put the checking of the body onto a "work list" // to avoid recursion here. if (functionNode->Body) { auto oldFunc = function; this->function = functionNode; checkStmt(functionNode->Body); this->function = oldFunc; } } } void getGenericParams( GenericDecl* decl, List& outParams, List outConstraints) { for (auto dd : decl->Members) { if (dd == decl->inner) continue; if (auto typeParamDecl = dd.As()) outParams.Add(typeParamDecl); else if (auto valueParamDecl = dd.As()) outParams.Add(valueParamDecl); else if (auto constraintDecl = dd.As()) outConstraints.Add(constraintDecl); } } bool doGenericSignaturesMatch( GenericDecl* fst, GenericDecl* snd) { // First we'll extract the parameters and constraints // in each generic signature. We will consider parameters // and constraints separately so that we are independent // of the order in which constraints are given (that is, // a constraint like `` whould be considered // the same as `` with a later `where T : IFoo`. List fstParams; List fstConstraints; getGenericParams(fst, fstParams, fstConstraints); List sndParams; List sndConstraints; getGenericParams(snd, sndParams, sndConstraints); // For there to be any hope of a match, the // two need to have the same number of parameters. UInt paramCount = fstParams.Count(); if (paramCount != sndParams.Count()) return false; // Now we'll walk through the parameters. for (UInt pp = 0; pp < paramCount; ++pp) { Decl* fstParam = fstParams[pp]; Decl* sndParam = sndParams[pp]; if (auto fstTypeParam = dynamic_cast(fstParam)) { if (auto sndTypeParam = dynamic_cast(sndParam)) { // TODO: is there any validation that needs to be peformed here? } else { // Type and non-type parameters can't match. return false; } } else if (auto fstValueParam = dynamic_cast(fstParam)) { if (auto sndValueParam = dynamic_cast(sndParam)) { // Need to check that the parameters have the same type. // // Note: We are assuming here that the type of a value // parameter cannot be dependent on any of the type // parameters in the same signature. This is a reasonable // assumption for now, but could get thorny down the road. if (!fstValueParam->getType()->Equals(sndValueParam->getType())) { // Type mismatch. return false; } // TODO: This is not the right place to check on default // values for the parameter, because they won't affect // the signature, but we should make sure to do validation // later on (e.g., that only one declaration can/should // be allowed to provide a default). } else { // Value and non-value parameters can't match. return false; } } } // If we got this far, then it means the parameter signatures *seem* // to match up all right, but now we need to check that the constraints // placed on those parameters are also consistent. // // For now I'm going to assume/require that all declarations must // declare the signature in a way that matches exactly. UInt constraintCount = fstConstraints.Count(); if(constraintCount != sndConstraints.Count()) return false; for (UInt cc = 0; cc < constraintCount; ++cc) { //auto fstConstraint = fstConstraints[cc]; //auto sndConstraint = sndConstraints[cc]; // TODO: the challenge here is that the // constraints are going to be expressed // in terms of the parameters, which means // we need to be doing substitution here. } // HACK: okay, we'll just assume things match for now. return true; } // Check if two functions have the same signature for the purposes // of overload resolution. bool doFunctionSignaturesMatch( DeclRef fst, DeclRef snd) { // TODO(tfoley): This copies the parameter array, which is bad for performance. auto fstParams = GetParameters(fst).ToArray(); auto sndParams = GetParameters(snd).ToArray(); // If the functions have different numbers of parameters, then // their signatures trivially don't match. auto fstParamCount = fstParams.Count(); auto sndParamCount = sndParams.Count(); if (fstParamCount != sndParamCount) return false; for (UInt ii = 0; ii < fstParamCount; ++ii) { auto fstParam = fstParams[ii]; auto sndParam = sndParams[ii]; // If a given parameter type doesn't match, then signatures don't match if (!GetType(fstParam)->Equals(GetType(sndParam))) return false; // If one parameter is `out` and the other isn't, then they don't match // // Note(tfoley): we don't consider `out` and `inout` as distinct here, // because there is no way for overload resolution to pick between them. if (fstParam.getDecl()->HasModifier() != sndParam.getDecl()->HasModifier()) return false; // If one parameter is `ref` and the other isn't, then they don't match. // if(fstParam.getDecl()->HasModifier() != sndParam.getDecl()->HasModifier()) return false; } // Note(tfoley): return type doesn't enter into it, because we can't take // calling context into account during overload resolution. return true; } RefPtr createDummySubstitutions( GenericDecl* genericDecl) { RefPtr subst = new GenericSubstitution(); subst->genericDecl = genericDecl; for (auto dd : genericDecl->Members) { if (dd == genericDecl->inner) continue; if (auto typeParam = dd.As()) { auto type = DeclRefType::Create(getSession(), makeDeclRef(typeParam.Ptr())); subst->args.Add(type); } else if (auto valueParam = dd.As()) { auto val = new GenericParamIntVal( makeDeclRef(valueParam.Ptr())); subst->args.Add(val); } // TODO: need to handle constraints here? } return subst; } void ValidateFunctionRedeclaration(FuncDecl* funcDecl) { auto parentDecl = funcDecl->ParentDecl; SLANG_ASSERT(parentDecl); if (!parentDecl) return; Decl* childDecl = funcDecl; // If this is a generic function (that is, its parent // declaration is a generic), then we need to look // for sibling declarations of the parent. auto genericDecl = dynamic_cast(parentDecl); if (genericDecl) { parentDecl = genericDecl->ParentDecl; childDecl = genericDecl; } // Look at previously-declared functions with the same name, // in the same container // // Note: there is an assumption here that declarations that // occur earlier in the program text will be *later* in // the linked list of declarations with the same name. // We are also assuming/requiring that the check here is // symmetric, in that it is okay to test (A,B) or (B,A), // and there is no need to test both. // buildMemberDictionary(parentDecl); for (auto pp = childDecl->nextInContainerWithSameName; pp; pp = pp->nextInContainerWithSameName) { auto prevDecl = pp; // Look through generics to the declaration underneath auto prevGenericDecl = dynamic_cast(prevDecl); if (prevGenericDecl) prevDecl = prevGenericDecl->inner.Ptr(); // We only care about previously-declared functions // Note(tfoley): although we should really error out if the // name is already in use for something else, like a variable... auto prevFuncDecl = dynamic_cast(prevDecl); if (!prevFuncDecl) continue; // If one declaration is a prefix/postfix operator, and the // other is not a matching operator, then don't consider these // to be redeclarations. // // Note(tfoley): Any attempt to call such an operator using // ordinary function-call syntax (if we decided to allow it) // would be ambiguous in such a case, of course. // if (funcDecl->HasModifier() != prevDecl->HasModifier()) continue; if (funcDecl->HasModifier() != prevDecl->HasModifier()) continue; // If one is generic and the other isn't, then there is no match. if ((genericDecl != nullptr) != (prevGenericDecl != nullptr)) continue; // We are going to be comparing the signatures of the // two functions, but if they are *generic* functions // then we will need to compare them with consistent // specializations in place. // // We'll go ahead and create some (unspecialized) declaration // references here, just to be prepared. DeclRef funcDeclRef(funcDecl, nullptr); DeclRef prevFuncDeclRef(prevFuncDecl, nullptr); // If we are working with generic functions, then we need to // consider if their generic signatures match. if (genericDecl) { SLANG_ASSERT(prevGenericDecl); // already checked above if (!doGenericSignaturesMatch(genericDecl, prevGenericDecl)) continue; // Now we need specialize the declaration references // consistently, so that we can compare. // // First we create a "dummy" set of substitutions that // just reference the parameters of the first generic. auto subst = createDummySubstitutions(genericDecl); // // Then we use those parameters to specialize the *other* // generic. // subst->genericDecl = prevGenericDecl; prevFuncDeclRef.substitutions.substitutions = subst; // // One way to think about it is that if we have these // declarations (ignore the name differences...): // // // prevFuncDecl: // void foo1(T x); // // // funcDecl: // void foo2(U x); // // Then we will compare `foo2` against `foo1`. } // If the parameter signatures don't match, then don't worry if (!doFunctionSignaturesMatch(funcDeclRef, prevFuncDeclRef)) continue; // If we get this far, then we've got two declarations in the same // scope, with the same name and signature, so they appear // to be redeclarations. // // We will track that redeclaration occured, so that we can // take it into account for overload resolution. // // A huge complication that we'll need to deal with is that // multiple declarations might introduce default values for // (different) parameters, and we might need to merge across // all of them (which could get complicated if defaults for // parameters can reference earlier parameters). // If the previous declaration wasn't already recorded // as being part of a redeclaration family, then make // it the primary declaration of a new family. if (!prevFuncDecl->primaryDecl) { prevFuncDecl->primaryDecl = prevFuncDecl; } // The new declaration will belong to the family of // the previous one, and so it will share the same // primary declaration. funcDecl->primaryDecl = prevFuncDecl->primaryDecl; funcDecl->nextDecl = nullptr; // Next we want to chain the new declaration onto // the linked list of redeclarations. auto link = &prevFuncDecl->nextDecl; while (*link) link = &(*link)->nextDecl; *link = funcDecl; // Now that we've added things to a group of redeclarations, // we can do some additional validation. // First, we will ensure that the return types match // between the declarations, so that they are truly // interchangeable. // // Note(tfoley): If we ever decide to add a beefier type // system to Slang, we might allow overloads like this, // so long as the desired result type can be disambiguated // based on context at the call type. In that case we would // consider result types earlier, as part of the signature // matching step. // auto resultType = GetResultType(funcDeclRef); auto prevResultType = GetResultType(prevFuncDeclRef); if (!resultType->Equals(prevResultType)) { // Bad dedeclaration getSink()->diagnose(funcDecl, Diagnostics::unimplemented, "redeclaration has a different return type"); // Don't bother emitting other errors at this point break; } // Note(tfoley): several of the following checks should // really be looping over all the previous declarations // in the same group, and not just the one previous // declaration we found just now. // TODO: Enforce that the new declaration had better // not specify a default value for any parameter that // already had a default value in a prior declaration. // We are going to want to enforce that we cannot have // two declarations of a function both specify bodies. // Before we make that check, however, we need to deal // with the case where the two function declarations // might represent different target-specific versions // of a function. // // TODO: if the two declarations are specialized for // different targets, then skip the body checks below. // If both of the declarations have a body, then there // is trouble, because we wouldn't know which one to // use during code generation. if (funcDecl->Body && prevFuncDecl->Body) { // Redefinition getSink()->diagnose(funcDecl, Diagnostics::unimplemented, "function redefinition"); // Don't bother emitting other errors break; } // At this point we've processed the redeclaration and // put it into a group, so there is no reason to keep // looping and looking at prior declarations. return; } } void visitScopeDecl(ScopeDecl*) { // Nothing to do } void visitParamDecl(ParamDecl* paramDecl) { // TODO: This logic should be shared with the other cases of // variable declarations. The main reason I am not doing it // yet is that we use a `ParamDecl` with a null type as a // special case in attribute declarations, and that could // trip up the ordinary variable checks. auto typeExpr = paramDecl->type; if(typeExpr.exp) { typeExpr = CheckUsableType(typeExpr); paramDecl->type = typeExpr; } paramDecl->SetCheckState(DeclCheckState::CheckedHeader); // The "initializer" expression for a parameter represents // a default argument value to use if an explicit one is // not supplied. if(auto initExpr = paramDecl->initExpr) { // We must check the expression and coerce it to the // actual type of the parameter. // initExpr = CheckExpr(initExpr); initExpr = Coerce(typeExpr.type, initExpr); paramDecl->initExpr = initExpr; // TODO: a default argument expression needs to // conform to other constraints to be valid. // For example, it should not be allowed to refer // to other parameters of the same function (or maybe // only the parameters to its left...). } paramDecl->SetCheckState(DeclCheckState::Checked); } void VisitFunctionDeclaration(FuncDecl *functionNode) { if (functionNode->IsChecked(DeclCheckState::CheckedHeader)) return; functionNode->SetCheckState(DeclCheckState::CheckingHeader); auto oldFunc = this->function; this->function = functionNode; auto returnType = CheckProperType(functionNode->ReturnType); functionNode->ReturnType = returnType; HashSet paraNames; for (auto & para : functionNode->GetParameters()) { EnsureDecl(para, DeclCheckState::CheckedHeader); if (paraNames.Contains(para->getName())) { getSink()->diagnose(para, Diagnostics::parameterAlreadyDefined, para->getName()); } else paraNames.Add(para->getName()); } this->function = oldFunc; functionNode->SetCheckState(DeclCheckState::CheckedHeader); // One last bit of validation: check if we are redeclaring an existing function ValidateFunctionRedeclaration(functionNode); } void visitDeclStmt(DeclStmt* stmt) { // We directly dispatch here instead of using `EnsureDecl()` for two // reasons: // // 1. We expect that a local declaration won't have been referenced // before it is declared, so that we can just check things in-order // // 2. `EnsureDecl()` is specialized for `Decl*` instead of `DeclBase*` // and trying to special case `DeclGroup*` here feels silly. // dispatchDecl(stmt->decl); } void visitBlockStmt(BlockStmt* stmt) { checkStmt(stmt->body); } void visitSeqStmt(SeqStmt* stmt) { for(auto ss : stmt->stmts) { checkStmt(ss); } } template T* FindOuterStmt() { UInt outerStmtCount = outerStmts.Count(); for (UInt ii = outerStmtCount; ii > 0; --ii) { auto outerStmt = outerStmts[ii-1]; auto found = dynamic_cast(outerStmt); if (found) return found; } return nullptr; } void visitBreakStmt(BreakStmt *stmt) { auto outer = FindOuterStmt(); if (!outer) { getSink()->diagnose(stmt, Diagnostics::breakOutsideLoop); } stmt->parentStmt = outer; } void visitContinueStmt(ContinueStmt *stmt) { auto outer = FindOuterStmt(); if (!outer) { getSink()->diagnose(stmt, Diagnostics::continueOutsideLoop); } stmt->parentStmt = outer; } void PushOuterStmt(Stmt* stmt) { outerStmts.Add(stmt); } void PopOuterStmt(Stmt* /*stmt*/) { outerStmts.RemoveAt(outerStmts.Count() - 1); } RefPtr checkPredicateExpr(Expr* expr) { RefPtr e = expr; e = CheckTerm(e); e = Coerce(getSession()->getBoolType(), e); return e; } void visitDoWhileStmt(DoWhileStmt *stmt) { PushOuterStmt(stmt); stmt->Predicate = checkPredicateExpr(stmt->Predicate); checkStmt(stmt->Statement); PopOuterStmt(stmt); } void visitForStmt(ForStmt *stmt) { PushOuterStmt(stmt); checkStmt(stmt->InitialStatement); if (stmt->PredicateExpression) { stmt->PredicateExpression = checkPredicateExpr(stmt->PredicateExpression); } if (stmt->SideEffectExpression) { stmt->SideEffectExpression = CheckExpr(stmt->SideEffectExpression); } checkStmt(stmt->Statement); PopOuterStmt(stmt); } RefPtr checkExpressionAndExpectIntegerConstant(RefPtr expr, RefPtr* outIntVal) { expr = CheckExpr(expr); auto intVal = CheckIntegerConstantExpression(expr); if (outIntVal) *outIntVal = intVal; return expr; } void visitCompileTimeForStmt(CompileTimeForStmt* stmt) { PushOuterStmt(stmt); stmt->varDecl->type.type = getSession()->getIntType(); addModifier(stmt->varDecl, new ConstModifier()); stmt->varDecl->SetCheckState(DeclCheckState::Checked); RefPtr rangeBeginVal; RefPtr rangeEndVal; if (stmt->rangeBeginExpr) { stmt->rangeBeginExpr = checkExpressionAndExpectIntegerConstant(stmt->rangeBeginExpr, &rangeBeginVal); } else { RefPtr rangeBeginConst = new ConstantIntVal(); rangeBeginConst->value = 0; rangeBeginVal = rangeBeginConst; } stmt->rangeEndExpr = checkExpressionAndExpectIntegerConstant(stmt->rangeEndExpr, &rangeEndVal); stmt->rangeBeginVal = rangeBeginVal; stmt->rangeEndVal = rangeEndVal; checkStmt(stmt->body); PopOuterStmt(stmt); } void visitSwitchStmt(SwitchStmt* stmt) { PushOuterStmt(stmt); // TODO(tfoley): need to coerce condition to an integral type... stmt->condition = CheckExpr(stmt->condition); checkStmt(stmt->body); // TODO(tfoley): need to check that all case tags are unique // TODO(tfoley): check that there is at most one `default` clause PopOuterStmt(stmt); } void visitCaseStmt(CaseStmt* stmt) { // TODO(tfoley): Need to coerce to type being switch on, // and ensure that value is a compile-time constant auto expr = CheckExpr(stmt->expr); auto switchStmt = FindOuterStmt(); if (!switchStmt) { getSink()->diagnose(stmt, Diagnostics::caseOutsideSwitch); } else { // TODO: need to do some basic matching to ensure the type // for the `case` is consistent with the type for the `switch`... } stmt->expr = expr; stmt->parentStmt = switchStmt; } void visitDefaultStmt(DefaultStmt* stmt) { auto switchStmt = FindOuterStmt(); if (!switchStmt) { getSink()->diagnose(stmt, Diagnostics::defaultOutsideSwitch); } stmt->parentStmt = switchStmt; } void visitIfStmt(IfStmt *stmt) { stmt->Predicate = checkPredicateExpr(stmt->Predicate); checkStmt(stmt->PositiveStatement); checkStmt(stmt->NegativeStatement); } void visitUnparsedStmt(UnparsedStmt*) { // Nothing to do } void visitEmptyStmt(EmptyStmt*) { // Nothing to do } void visitDiscardStmt(DiscardStmt*) { // Nothing to do } void visitReturnStmt(ReturnStmt *stmt) { if (!stmt->Expression) { if (function && !function->ReturnType.Equals(getSession()->getVoidType())) { getSink()->diagnose(stmt, Diagnostics::returnNeedsExpression); } } else { stmt->Expression = CheckTerm(stmt->Expression); if (!stmt->Expression->type->Equals(getSession()->getErrorType())) { if (function) { stmt->Expression = Coerce(function->ReturnType.Ptr(), stmt->Expression); } else { // TODO(tfoley): this case currently gets triggered for member functions, // which aren't being checked consistently (because of the whole symbol // table idea getting in the way). // getSink()->diagnose(stmt, Diagnostics::unimplemented, "case for return stmt"); } } } } IntegerLiteralValue GetMinBound(RefPtr val) { if (auto constantVal = val.As()) return constantVal->value; // TODO(tfoley): Need to track intervals so that this isn't just a lie... return 1; } void maybeInferArraySizeForVariable(Variable* varDecl) { // Not an array? auto arrayType = varDecl->type->AsArrayType(); if (!arrayType) return; // Explicit element count given? auto elementCount = arrayType->ArrayLength; if (elementCount) return; // No initializer? auto initExpr = varDecl->initExpr; if(!initExpr) return; // Is the initializer an initializer list? if(auto initializerListExpr = initExpr.As()) { auto argCount = initializerListExpr->args.Count(); elementCount = new ConstantIntVal(argCount); } // Is the type of the initializer an array type? else if(auto arrayInitType = initExpr->type->As()) { elementCount = arrayInitType->ArrayLength; } else { // Nothing to do: we couldn't infer a size return; } // Create a new array type based on the size we found, // and install it into our type. varDecl->type.type = getArrayType( arrayType->baseType, elementCount); } void ValidateArraySizeForVariable(Variable* varDecl) { auto arrayType = varDecl->type->AsArrayType(); if (!arrayType) return; auto elementCount = arrayType->ArrayLength; if (!elementCount) { // Note(tfoley): For now we allow arrays of unspecified size // everywhere, because some source languages (e.g., GLSL) // allow them in specific cases. #if 0 getSink()->diagnose(varDecl, Diagnostics::invalidArraySize); #endif return; } // TODO(tfoley): How to handle the case where bound isn't known? if (GetMinBound(elementCount) <= 0) { getSink()->diagnose(varDecl, Diagnostics::invalidArraySize); return; } } void visitVariable(Variable* varDecl) { if (function || checkingPhase == CheckingPhase::Header) { TypeExp typeExp = CheckUsableType(varDecl->type); #if 0 if (typeExp.type->GetBindableResourceType() != BindableResourceType::NonBindable) { // We don't want to allow bindable resource types as local variables (at least for now). auto parentDecl = varDecl->ParentDecl; if (auto parentScopeDecl = dynamic_cast(parentDecl)) { getSink()->diagnose(varDecl->type, Diagnostics::invalidTypeForLocalVariable); } } #endif varDecl->type = typeExp; if (varDecl->type.Equals(getSession()->getVoidType())) { getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid); } } if (checkingPhase == CheckingPhase::Body) { if (auto initExpr = varDecl->initExpr) { initExpr = CheckTerm(initExpr); varDecl->initExpr = initExpr; } // If this is an array variable, then we first want to give // it a chance to infer an array size from its initializer // // TODO(tfoley): May need to extend this to handle the // multi-dimensional case... maybeInferArraySizeForVariable(varDecl); // // Next we want to make sure that the declared (or inferred) // size for the array meets whatever language-specific // constraints we want to enforce (e.g., disallow empty // arrays in specific cases) ValidateArraySizeForVariable(varDecl); if (auto initExpr = varDecl->initExpr) { // TODO(tfoley): should coercion of initializer lists be special-cased // here, or handled as a general case for coercion? initExpr = Coerce(varDecl->type.Ptr(), initExpr); varDecl->initExpr = initExpr; } } varDecl->SetCheckState(getCheckedState()); } void visitWhileStmt(WhileStmt *stmt) { PushOuterStmt(stmt); stmt->Predicate = checkPredicateExpr(stmt->Predicate); checkStmt(stmt->Statement); PopOuterStmt(stmt); } void visitExpressionStmt(ExpressionStmt *stmt) { stmt->Expression = CheckExpr(stmt->Expression); } RefPtr visitBoolLiteralExpr(BoolLiteralExpr* expr) { expr->type = getSession()->getBoolType(); return expr; } RefPtr visitIntegerLiteralExpr(IntegerLiteralExpr* expr) { // The expression might already have a type, determined by its suffix. // It it doesn't, we will give it a default type. // // TODO: We should be careful to pick a "big enough" type // based on the size of the value (e.g., don't try to stuff // a constant in an `int` if it requires 64 or more bits). // // The long-term solution here is to give a type to a literal // based on the context where it is used, but that requires // a more sophisticated type system than we have today. // if(!expr->type.type) { expr->type = getSession()->getIntType(); } return expr; } RefPtr visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) { if(!expr->type.type) { expr->type = getSession()->getFloatType(); } return expr; } RefPtr visitStringLiteralExpr(StringLiteralExpr* expr) { expr->type = getSession()->getStringType(); return expr; } IntVal* GetIntVal(IntegerLiteralExpr* expr) { // TODO(tfoley): don't keep allocating here! return new ConstantIntVal(expr->value); } Name* getName(String const& text) { return getCompileRequest()->getNamePool()->getName(text); } RefPtr TryConstantFoldExpr( InvokeExpr* invokeExpr) { // We need all the operands to the expression // Check if the callee is an operation that is amenable to constant-folding. // // For right now we will look for calls to intrinsic functions, and then inspect // their names (this is bad and slow). auto funcDeclRefExpr = invokeExpr->FunctionExpr.As(); if (!funcDeclRefExpr) return nullptr; auto funcDeclRef = funcDeclRefExpr->declRef; auto intrinsicMod = funcDeclRef.getDecl()->FindModifier(); if (!intrinsicMod) { // We can't constant fold anything that doesn't map to a builtin // operation right now. // // TODO: we should really allow constant-folding for anything // that can be lowerd to our bytecode... return nullptr; } // Let's not constant-fold operations with more than a certain number of arguments, for simplicity static const int kMaxArgs = 8; if (invokeExpr->Arguments.Count() > kMaxArgs) return nullptr; // Before checking the operation name, let's look at the arguments RefPtr argVals[kMaxArgs]; IntegerLiteralValue constArgVals[kMaxArgs]; int argCount = 0; bool allConst = true; for (auto argExpr : invokeExpr->Arguments) { auto argVal = TryCheckIntegerConstantExpression(argExpr.Ptr()); if (!argVal) return nullptr; argVals[argCount] = argVal; if (auto constArgVal = argVal.As()) { constArgVals[argCount] = constArgVal->value; } else { allConst = false; } argCount++; } if (!allConst) { // TODO(tfoley): We probably want to support a very limited number of operations // on "constants" that aren't actually known, to be able to handle a generic // that takes an integer `N` but then constructs a vector of size `N+1`. // // The hard part there is implementing the rules for value unification in the // presence of more complicated `IntVal` subclasses, like `SumIntVal`. You'd // need inference to be smart enough to know that `2 + N` and `N + 2` are the // same value, as are `N + M + 1 + 1` and `M + 2 + N`. // // For now we can just bail in this case. return nullptr; } // At this point, all the operands had simple integer values, so we are golden. IntegerLiteralValue resultValue = 0; auto opName = funcDeclRef.GetName(); // handle binary operators if (opName == getName("-")) { if (argCount == 1) { resultValue = -constArgVals[0]; } else if (argCount == 2) { resultValue = constArgVals[0] - constArgVals[1]; } } // simple binary operators #define CASE(OP) \ else if(opName == getName(#OP)) do { \ if(argCount != 2) return nullptr; \ resultValue = constArgVals[0] OP constArgVals[1]; \ } while(0) CASE(+); // TODO: this can also be unary... CASE(*); #undef CASE // binary operators with chance of divide-by-zero // TODO: issue a suitable error in that case #define CASE(OP) \ else if(opName == getName(#OP)) do { \ if(argCount != 2) return nullptr; \ if(!constArgVals[1]) return nullptr; \ resultValue = constArgVals[0] OP constArgVals[1]; \ } while(0) CASE(/); CASE(%); #undef CASE // TODO(tfoley): more cases else { return nullptr; } RefPtr result = new ConstantIntVal(resultValue); return result; } RefPtr TryConstantFoldExpr( Expr* expr) { // Unwrap any "identity" expressions while (auto parenExpr = dynamic_cast(expr)) { expr = parenExpr->base; } // TODO(tfoley): more serious constant folding here if (auto intLitExpr = dynamic_cast(expr)) { return GetIntVal(intLitExpr); } // it is possible that we are referring to a generic value param if (auto declRefExpr = dynamic_cast(expr)) { auto declRef = declRefExpr->declRef; if (auto genericValParamRef = declRef.As()) { // TODO(tfoley): handle the case of non-`int` value parameters... return new GenericParamIntVal(genericValParamRef); } // We may also need to check for references to variables that // are defined in a way that can be used as a constant expression: if(auto varRef = declRef.As()) { auto varDecl = varRef.getDecl(); switch(getSourceLanguage()) { default: case SourceLanguage::Slang: case SourceLanguage::HLSL: // HLSL: `static const` is used to mark compile-time constant expressions if(auto staticAttr = varDecl->FindModifier()) { if(auto constAttr = varDecl->FindModifier()) { // HLSL `static const` can be used as a constant expression if(auto initExpr = getInitExpr(varRef)) { return TryConstantFoldExpr(initExpr.Ptr()); } } } break; case SourceLanguage::GLSL: // GLSL: `const` indicates compile-time constant expression // // TODO(tfoley): The current logic here isn't robust against // GLSL "specialization constants" - we will extract the // initializer for a `const` variable and use it to extract // a value, when we really should be using an opaque // reference to the variable. if(auto constAttr = varDecl->FindModifier()) { // We need to handle a "specialization constant" (with a `constant_id` layout modifier) // differently from an ordinary compile-time constant. The latter can/should be reduced // to a value, while the former should be kept as a symbolic reference if(auto constantIDModifier = varDecl->FindModifier()) { // Retain the specialization constant as a symbolic reference // // TODO(tfoley): handle the case of non-`int` value parameters... // // TODO(tfoley): this is cloned from the case above that handles generic value parameters return new GenericParamIntVal(varRef); } else if(auto initExpr = getInitExpr(varRef)) { // This is an ordinary constant, and not a specialization constant, so we // can try to fold its value right now. return TryConstantFoldExpr(initExpr.Ptr()); } } break; } } } if(auto castExpr = dynamic_cast(expr)) { auto val = TryConstantFoldExpr(castExpr->Arguments[0].Ptr()); if(val) return val; } else if (auto invokeExpr = dynamic_cast(expr)) { auto val = TryConstantFoldExpr(invokeExpr); if (val) return val; } return nullptr; } // Try to check an integer constant expression, either returning the value, // or NULL if the expression isn't recognized as a constant. RefPtr TryCheckIntegerConstantExpression(Expr* exp) { // Check if type is acceptable for an integer constant expression if(auto basicType = exp->type.type->As()) { switch(basicType->baseType) { default: return nullptr; case BaseType::Int: case BaseType::UInt: case BaseType::UInt64: break; } } else { return nullptr; } // Consider operations that we might be able to constant-fold... return TryConstantFoldExpr(exp); } // Enforce that an expression resolves to an integer constant, and get its value RefPtr CheckIntegerConstantExpression(Expr* inExpr) { // No need to issue further errors if the expression didn't even type-check. if(IsErrorExpr(inExpr)) return nullptr; // First coerce the expression to the expected type auto expr = Coerce(getSession()->getIntType(),inExpr); // No need to issue further errors if the type coercion failed. if(IsErrorExpr(expr)) return nullptr; auto result = TryCheckIntegerConstantExpression(expr.Ptr()); if (!result) { getSink()->diagnose(expr, Diagnostics::expectedIntegerConstantNotConstant); } return result; } RefPtr CheckSimpleSubscriptExpr( RefPtr subscriptExpr, RefPtr elementType) { auto baseExpr = subscriptExpr->BaseExpression; auto indexExpr = subscriptExpr->IndexExpression; if (!indexExpr->type->Equals(getSession()->getIntType()) && !indexExpr->type->Equals(getSession()->getUIntType())) { getSink()->diagnose(indexExpr, Diagnostics::subscriptIndexNonInteger); return CreateErrorExpr(subscriptExpr.Ptr()); } subscriptExpr->type = QualType(elementType); // TODO(tfoley): need to be more careful about this stuff subscriptExpr->type.IsLeftValue = baseExpr->type.IsLeftValue; return subscriptExpr; } // The way that we have designed out type system, pretyt much *every* // type is a reference to some declaration in the standard library. // That means that when we construct a new type on the fly, we need // to make sure that it is wired up to reference the appropriate // declaration, or else it won't compare as equal to other types // that *do* reference the declaration. // // This function is used to construct a `vector` type // programmatically, so that it will work just like a type of // that form constructed by the user. RefPtr createVectorType( RefPtr elementType, RefPtr elementCount) { auto session = getSession(); auto vectorGenericDecl = findMagicDecl( session, "Vector").As(); auto vectorTypeDecl = vectorGenericDecl->inner; auto substitutions = new GenericSubstitution(); substitutions->genericDecl = vectorGenericDecl.Ptr(); substitutions->args.Add(elementType); substitutions->args.Add(elementCount); auto declRef = DeclRef(vectorTypeDecl.Ptr(), substitutions); return DeclRefType::Create( session, declRef)->As(); } RefPtr visitIndexExpr(IndexExpr* subscriptExpr) { auto baseExpr = subscriptExpr->BaseExpression; baseExpr = CheckExpr(baseExpr); RefPtr indexExpr = subscriptExpr->IndexExpression; if (indexExpr) { indexExpr = CheckExpr(indexExpr); } subscriptExpr->BaseExpression = baseExpr; subscriptExpr->IndexExpression = indexExpr; // If anything went wrong in the base expression, // then just move along... if (IsErrorExpr(baseExpr)) return CreateErrorExpr(subscriptExpr); // Otherwise, we need to look at the type of the base expression, // to figure out how subscripting should work. auto baseType = baseExpr->type.Ptr(); if (auto baseTypeType = baseType->As()) { // We are trying to "index" into a type, so we have an expression like `float[2]` // which should be interpreted as resolving to an array type. RefPtr elementCount = nullptr; if (indexExpr) { elementCount = CheckIntegerConstantExpression(indexExpr.Ptr()); } auto elementType = CoerceToUsableType(TypeExp(baseExpr, baseTypeType->type)); auto arrayType = getArrayType( elementType, elementCount); typeResult = arrayType; subscriptExpr->type = QualType(getTypeType(arrayType)); return subscriptExpr; } else if (auto baseArrayType = baseType->As()) { return CheckSimpleSubscriptExpr( subscriptExpr, baseArrayType->baseType); } else if (auto vecType = baseType->As()) { return CheckSimpleSubscriptExpr( subscriptExpr, vecType->elementType); } else if (auto matType = baseType->As()) { // TODO(tfoley): We shouldn't go and recompute // row types over and over like this... :( auto rowType = createVectorType( matType->getElementType(), matType->getColumnCount()); return CheckSimpleSubscriptExpr( subscriptExpr, rowType); } // Default behavior is to look at all available `__subscript` // declarations on the type and try to call one of them. { LookupResult lookupResult = lookUpMember( getSession(), this, getName("operator[]"), baseType); if (!lookupResult.isValid()) { goto fail; } // Now that we know there is at least one subscript member, // we will construct a reference to it and try to call it. // // Note: the expression may be an `OverloadedExpr`, in which // case the attempt to call it will trigger overload // resolution. RefPtr subscriptFuncExpr = createLookupResultExpr( lookupResult, subscriptExpr->BaseExpression, subscriptExpr->loc); RefPtr subscriptCallExpr = new InvokeExpr(); subscriptCallExpr->loc = subscriptExpr->loc; subscriptCallExpr->FunctionExpr = subscriptFuncExpr; // TODO(tfoley): This path can support multiple arguments easily subscriptCallExpr->Arguments.Add(subscriptExpr->IndexExpression); return CheckInvokeExprWithCheckedOperands(subscriptCallExpr.Ptr()); } fail: { getSink()->diagnose(subscriptExpr, Diagnostics::subscriptNonArray, baseType); return CreateErrorExpr(subscriptExpr); } } bool MatchArguments(FuncDecl * functionNode, List > &args) { if (functionNode->GetParameters().Count() != args.Count()) return false; int i = 0; for (auto param : functionNode->GetParameters()) { if (!param->type.Equals(args[i]->type.Ptr())) return false; i++; } return true; } // Coerce an expression to a specific type that it is expected to have in context RefPtr CoerceExprToType( RefPtr expr, RefPtr type) { // TODO(tfoley): clean this up so there is only one version... return Coerce(type, expr); } RefPtr visitParenExpr(ParenExpr* expr) { auto base = expr->base; base = CheckTerm(base); expr->base = base; expr->type = base->type; return expr; } // RefPtr visitAssignExpr(AssignExpr* expr) { expr->left = CheckExpr(expr->left); auto type = expr->left->type; expr->right = Coerce(type, CheckTerm(expr->right)); if (!type.IsLeftValue) { if (type->As()) { // Don't report an l-value issue on an errorneous expression } else { getSink()->diagnose(expr, Diagnostics::assignNonLValue); // As a special case, check if the LHS expression is derived // from a `this` parameter (implicitly or explicitly), which // is immutable. We can give the user a bit more context into // what is going on. // // We will try to handle expressions of the form: // // e ::= "this" // | e . name // | e [ expr ] // // We will unwrap the `e.name` and `e[expr]` cases in a loop. RefPtr e = expr->left; for(;;) { if(auto memberExpr = e.As()) { e = memberExpr->BaseExpression; } else if(auto subscriptExpr = e.As()) { e = subscriptExpr->BaseExpression; } else { break; } } // // Now we check to see if we have a `this` expression, // and if it is immutable. if(auto thisExpr = e.As()) { if(!thisExpr->type.IsLeftValue) { getSink()->diagnose(thisExpr, Diagnostics::thisIsImmutableByDefault); } } } } expr->type = type; return expr; } void registerExtension(ExtensionDecl* decl) { if (decl->IsChecked(DeclCheckState::CheckedHeader)) return; decl->SetCheckState(DeclCheckState::CheckingHeader); decl->targetType = CheckProperType(decl->targetType); decl->SetCheckState(DeclCheckState::CheckedHeader); // TODO: need to check that the target type names a declaration... if (auto targetDeclRefType = decl->targetType->As()) { // Attach our extension to that type as a candidate... if (auto aggTypeDeclRef = targetDeclRefType->declRef.As()) { auto aggTypeDecl = aggTypeDeclRef.getDecl(); decl->nextCandidateExtension = aggTypeDecl->candidateExtensions; aggTypeDecl->candidateExtensions = decl; return; } } getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here"); } void visitExtensionDecl(ExtensionDecl* decl) { if (decl->IsChecked(getCheckedState())) return; if (!decl->targetType->As()) { getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here"); } // now check the members of the extension for (auto m : decl->Members) { checkDecl(m); } decl->SetCheckState(getCheckedState()); } // Figure out what type an initializer/constructor declaration // is supposed to return. In most cases this is just the type // declaration that its declaration is nested inside. RefPtr findResultTypeForConstructorDecl(ConstructorDecl* decl) { // We want to look at the parent of the declaration, // but if the declaration is generic, the parent will be // the `GenericDecl` and we need to skip past that to // the grandparent. // auto parent = decl->ParentDecl; auto genericParent = dynamic_cast(parent); if (genericParent) { parent = genericParent->ParentDecl; } // Now look at the type of the parent (or grandparent). if (auto aggTypeDecl = dynamic_cast(parent)) { // We are nested in an aggregate type declaration, // so the result type of the initializer will just // be the surrounding type. return DeclRefType::Create( getSession(), makeDeclRef(aggTypeDecl)); } else if (auto extDecl = dynamic_cast(parent)) { // We are nested inside an extension, so the result // type needs to be the type being extended. return extDecl->targetType.type; } else { getSink()->diagnose(decl, Diagnostics::initializerNotInsideType); return nullptr; } } void visitConstructorDecl(ConstructorDecl* decl) { if (decl->IsChecked(getCheckedState())) return; if (checkingPhase == CheckingPhase::Header) { decl->SetCheckState(DeclCheckState::CheckingHeader); for (auto& paramDecl : decl->GetParameters()) { paramDecl->type = CheckUsableType(paramDecl->type); } // We need to compute the result tyep for this declaration, // since it wasn't filled in for us. decl->ReturnType.type = findResultTypeForConstructorDecl(decl); } else { // TODO(tfoley): check body } decl->SetCheckState(getCheckedState()); } void visitSubscriptDecl(SubscriptDecl* decl) { if (decl->IsChecked(getCheckedState())) return; for (auto& paramDecl : decl->GetParameters()) { paramDecl->type = CheckUsableType(paramDecl->type); } decl->ReturnType = CheckUsableType(decl->ReturnType); // If we have a subscript declaration with no accessor declarations, // then we should create a single `GetterDecl` to represent // the implicit meaning of their declaration, so: // // subscript(uint index) -> T; // // becomes: // // subscript(uint index) -> T { get; } // bool anyAccessors = false; for(auto accessorDecl : decl->getMembersOfType()) { anyAccessors = true; } if(!anyAccessors) { RefPtr getterDecl = new GetterDecl(); getterDecl->loc = decl->loc; getterDecl->ParentDecl = decl; decl->Members.Add(getterDecl); } for(auto mm : decl->Members) { checkDecl(mm); } decl->SetCheckState(getCheckedState()); } void visitAccessorDecl(AccessorDecl* decl) { if (checkingPhase == CheckingPhase::Header) { // An acessor must appear nested inside a subscript declaration (today), // or a property declaration (when we add them). It will derive // its return type from the outer declaration, so we handle both // of these checks at the same place. auto parent = decl->ParentDecl; if (auto parentSubscript = dynamic_cast(parent)) { decl->ReturnType = parentSubscript->ReturnType; } // TODO: when we add "property" declarations, check for them here else { getSink()->diagnose(decl, Diagnostics::accessorMustBeInsideSubscriptOrProperty); } } else { // TODO: check the body! } decl->SetCheckState(getCheckedState()); } // struct Constraint { Decl* decl; // the declaration of the thing being constraints RefPtr val; // the value to which we are constraining it bool satisfied = false; // Has this constraint been met? }; // A collection of constraints that will need to be satisified (solved) // in order for checking to suceed. struct ConstraintSystem { // A source location to use in reporting any issues SourceLoc loc; // The generic declaration whose parameters we // are trying to solve for. RefPtr genericDecl; // Constraints we have accumulated, which constrain // the possible arguments for those parameters. List constraints; }; RefPtr TryJoinVectorAndScalarType( RefPtr vectorType, RefPtr scalarType) { // Join( vector, S ) -> vetor // // That is, the join of a vector and a scalar type is // a vector type with a joined element type. auto joinElementType = TryJoinTypes( vectorType->elementType, scalarType); if(!joinElementType) return nullptr; return createVectorType( joinElementType, vectorType->elementCount); } struct TypeWitnessBreadcrumb { TypeWitnessBreadcrumb* prev; RefPtr sub; RefPtr sup; DeclRef declRef; }; // Crete a subtype witness based on the declared relationship // found in a single breadcrumb RefPtr createSimpleSubtypeWitness( TypeWitnessBreadcrumb* breadcrumb) { RefPtr witness = new DeclaredSubtypeWitness(); witness->sub = breadcrumb->sub; witness->sup = breadcrumb->sup; witness->declRef = breadcrumb->declRef; return witness; } RefPtr createTypeWitness( RefPtr type, DeclRef interfaceDeclRef, TypeWitnessBreadcrumb* inBreadcrumbs) { if(!inBreadcrumbs) { // We need to construct a witness to the fact // that `type` has been proven to be *equal* // to `interfaceDeclRef`. // SLANG_UNEXPECTED("reflexive type witness"); UNREACHABLE_RETURN(nullptr); } // We might have one or more steps in the breadcrumb trail, e.g.: // // {A : B} {B : C} {C : D} // // The chain is stored as a reversed linked list, so that // the first entry would be the `(C : D)` relationship // above. // // We need to walk the list and build up a suitable witness, // which in the above case would look like: // // Transitive( // Transitive( // Declared({A : B}), // {B : C}), // {C : D}) // // Because of the ordering of the breadcrumb trail, along // with the way the `Transitive` case nests, we will be // building these objects outside-in, and keeping // track of the "hole" where the next step goes. // auto bb = inBreadcrumbs; // `witness` here will hold the first (outer-most) object // we create, which is the overall result. RefPtr witness; // `link` will point at the remaining "hole" in the // data structure, to be filled in. RefPtr* link = &witness; // As long as there is more than one breadcrumb, we // need to be creating transitie witnesses. while(bb->prev) { // On the first iteration when processing the list // above, the breadcrumb would be for `{ C : D }`, // and so we'd create: // // Transitive( // [...], // { C : D}) // // where `[...]` represents the "hole" we leave // open to fill in next. // RefPtr transitiveWitness = new TransitiveSubtypeWitness(); transitiveWitness->sub = bb->sub; transitiveWitness->sup = bb->sup; transitiveWitness->midToSup = bb->declRef; // Fill in the current hole, and then set the // hole to point into the node we just created. *link = transitiveWitness; link = &transitiveWitness->subToMid; // Move on with the list. bb = bb->prev; } // If we exit the loop, then there is only one breadcrumb left. // In our running example this would be `{ A : B }`. We create // a simple (declared) subtype witness for it, and plug the // final hole, after which there shouldn't be a hole to deal with. RefPtr declaredWitness = createSimpleSubtypeWitness(bb); *link = declaredWitness; // We now know that our original `witness` variable has been // filled in, and there are no other holes. return witness; } bool doesTypeConformToInterfaceImpl( RefPtr originalType, RefPtr type, DeclRef interfaceDeclRef, RefPtr* outWitness, TypeWitnessBreadcrumb* inBreadcrumbs) { // for now look up a conformance member... if(auto declRefType = type->As()) { auto declRef = declRefType->declRef; // Easy case: a type conforms to itself. // // TODO: This is actually a bit more complicated, as // the interface needs to be "object-safe" for us to // really make this determination... if(declRef == interfaceDeclRef) { if(outWitness) { *outWitness = createTypeWitness(originalType, interfaceDeclRef, inBreadcrumbs); } return true; } if( auto aggTypeDeclRef = declRef.As() ) { checkDecl(aggTypeDeclRef.getDecl()); for( auto inheritanceDeclRef : getMembersOfTypeWithExt(aggTypeDeclRef)) { checkDecl(inheritanceDeclRef.getDecl()); // Here we will recursively look up conformance on the type // that is being inherited from. This is dangerous because // it might lead to infinite loops. // // TODO: A better appraoch would be to create a linearized list // of all the interfaces that a given type direclty or indirectly // inheirts, and store it with the type, so that we don't have // to recurse in places like this (and can maybe catch infinite // loops better). This would also help avoid checking multiply-inherited // conformances multiple times. auto inheritedType = getBaseType(inheritanceDeclRef); // We need to ensure that the witness that gets created // is a composite one, reflecting lookup through // the inheritance declaration. TypeWitnessBreadcrumb breadcrumb; breadcrumb.prev = inBreadcrumbs; breadcrumb.sub = type; breadcrumb.sup = inheritedType; breadcrumb.declRef = inheritanceDeclRef; if(doesTypeConformToInterfaceImpl(originalType, inheritedType, interfaceDeclRef, outWitness, &breadcrumb)) { return true; } } // if an inheritance decl is not found, try to find a GenericTypeConstraintDecl for (auto genConstraintDeclRef : getMembersOfType(aggTypeDeclRef)) { checkDecl(genConstraintDeclRef.getDecl()); auto inheritedType = GetSup(genConstraintDeclRef); TypeWitnessBreadcrumb breadcrumb; breadcrumb.prev = inBreadcrumbs; breadcrumb.sub = type; breadcrumb.sup = inheritedType; breadcrumb.declRef = genConstraintDeclRef; if (doesTypeConformToInterfaceImpl(originalType, inheritedType, interfaceDeclRef, outWitness, &breadcrumb)) { return true; } } } else if( auto genericTypeParamDeclRef = declRef.As() ) { // We need to enumerate the constraints placed on this type by its outer // generic declaration, and see if any of them guarantees that we // satisfy the given interface.. auto genericDeclRef = genericTypeParamDeclRef.GetParent().As(); SLANG_ASSERT(genericDeclRef); for( auto constraintDeclRef : getMembersOfType(genericDeclRef) ) { auto sub = GetSub(constraintDeclRef); auto sup = GetSup(constraintDeclRef); auto subDeclRef = sub->As(); if(!subDeclRef) continue; if(subDeclRef->declRef != genericTypeParamDeclRef) continue; // The witness that we create needs to reflect that // it found the needed conformance by lookup through // a generic type constraint. TypeWitnessBreadcrumb breadcrumb; breadcrumb.prev = inBreadcrumbs; breadcrumb.sub = sub; breadcrumb.sup = sup; breadcrumb.declRef = constraintDeclRef; if(doesTypeConformToInterfaceImpl(originalType, sup, interfaceDeclRef, outWitness, &breadcrumb)) { return true; } } } } // default is failure return false; } bool DoesTypeConformToInterface( RefPtr type, DeclRef interfaceDeclRef) { return doesTypeConformToInterfaceImpl(type, type, interfaceDeclRef, nullptr, nullptr); } RefPtr tryGetInterfaceConformanceWitness( RefPtr type, DeclRef interfaceDeclRef) { RefPtr result; doesTypeConformToInterfaceImpl(type, type, interfaceDeclRef, &result, nullptr); return result; } RefPtr TryJoinTypeWithInterface( RefPtr type, DeclRef interfaceDeclRef) { // The most basic test here should be: does the type declare conformance to the trait. if(DoesTypeConformToInterface(type, interfaceDeclRef)) return type; // There is a more nuanced case if `type` is a builtin type, and we need to make it // conform to a trait that some but not all builtin types support (the main problem // here is when an operation wants an integer type, but one of our operands is a `float`. // The HLSL rules will allow that, with implicit conversion, but our default join rules // will end up picking `float` and we don't want that...). // For now we don't handle the hard case and just bail return nullptr; } // Try to compute the "join" between two types RefPtr TryJoinTypes( RefPtr left, RefPtr right) { // Easy case: they are the same type! if (left->Equals(right)) return left; // We can join two basic types by picking the "better" of the two if (auto leftBasic = left->As()) { if (auto rightBasic = right->As()) { auto leftFlavor = leftBasic->baseType; auto rightFlavor = rightBasic->baseType; // TODO(tfoley): Need a special-case rule here that if // either operand is of type `half`, then we promote // to at least `float` // Return the one that had higher rank... if (leftFlavor > rightFlavor) return left; else { SLANG_ASSERT(rightFlavor > leftFlavor); // equality was handles at the top of this function return right; } } // We can also join a vector and a scalar if(auto rightVector = right->As()) { return TryJoinVectorAndScalarType(rightVector, leftBasic); } } // We can join two vector types by joining their element types // (and also their sizes...) if( auto leftVector = left->As()) { if(auto rightVector = right->As()) { // Check if the vector sizes match if(!leftVector->elementCount->EqualsVal(rightVector->elementCount.Ptr())) return nullptr; // Try to join the element types auto joinElementType = TryJoinTypes( leftVector->elementType, rightVector->elementType); if(!joinElementType) return nullptr; return createVectorType( joinElementType, leftVector->elementCount); } // We can also join a vector and a scalar if(auto rightBasic = right->As()) { return TryJoinVectorAndScalarType(leftVector, rightBasic); } } // HACK: trying to work trait types in here... if(auto leftDeclRefType = left->As()) { if( auto leftInterfaceRef = leftDeclRefType->declRef.As() ) { // return TryJoinTypeWithInterface(right, leftInterfaceRef); } } if(auto rightDeclRefType = right->As()) { if( auto rightInterfaceRef = rightDeclRefType->declRef.As() ) { // return TryJoinTypeWithInterface(left, rightInterfaceRef); } } // TODO: all the cases for vectors apply to matrices too! // Default case is that we just fail. return nullptr; } // Try to solve a system of generic constraints. // The `system` argument provides the constraints. // The `varSubst` argument provides the list of constraint // variables that were created for the system. // // Returns a new substitution representing the values that // we solved for along the way. SubstitutionSet TrySolveConstraintSystem( ConstraintSystem* system, DeclRef genericDeclRef) { // For now the "solver" is going to be ridiculously simplistic. // The generic itself will have some constraints, and for now we add these // to the system of constrains we will use for solving for the type variables. // // TODO: we need to decide whether constraints are used like this to influence // how we solve for type/value variables, or whether constraints in the parameter // list just work as a validation step *after* we've solved for the types. // // That is, should we allow `` to be written, and cause us to "infer" // that `T` should be the type `Int`? That seems a little silly. // // Eventually, though, we may want to support type identity constraints, especially // on associated types, like `` // These seem more reasonable to have influence constraint solving, since it could // conceivably let us specialize a `X : IContainer` to `X` if we find // that `X.IndexType == T`. for( auto constraintDeclRef : getMembersOfType(genericDeclRef) ) { if(!TryUnifyTypes(*system, GetSub(constraintDeclRef), GetSup(constraintDeclRef))) return SubstitutionSet(); } SubstitutionSet resultSubst = genericDeclRef.substitutions; // We will loop over the generic parameters, and for // each we will try to find a way to satisfy all // the constraints for that parameter List> args; for (auto m : getMembers(genericDeclRef)) { if (auto typeParam = m.As()) { RefPtr type = nullptr; for (auto& c : system->constraints) { if (c.decl != typeParam.getDecl()) continue; auto cType = c.val.As(); SLANG_RELEASE_ASSERT(cType.Ptr()); if (!type) { type = cType; } else { auto joinType = TryJoinTypes(type, cType); if (!joinType) { // failure! return SubstitutionSet(); } type = joinType; } c.satisfied = true; } if (!type) { // failure! return SubstitutionSet(); } args.Add(type); } else if (auto valParam = m.As()) { // TODO(tfoley): maybe support more than integers some day? // TODO(tfoley): figure out how this needs to interact with // compile-time integers that aren't just constants... RefPtr val = nullptr; for (auto& c : system->constraints) { if (c.decl != valParam.getDecl()) continue; auto cVal = c.val.As(); SLANG_RELEASE_ASSERT(cVal.Ptr()); if (!val) { val = cVal; } else { if(!val->EqualsVal(cVal.Ptr())) { // failure! return SubstitutionSet(); } } c.satisfied = true; } if (!val) { // failure! return SubstitutionSet(); } args.Add(val); } else { // ignore anything that isn't a generic parameter } } // After we've solved for the explicit arguments, we need to // make a second pass and consider the implicit arguments, // based on what we've already determined to be the values // for the explicit arguments. // Before we begin, we are going to go ahead and create the // "solved" substitution that we will return if everything works. // This is because we are going to use this substitution, // partially filled in with the results we know so far, // in order to specialize any constraints on the generic. // // E.g., if the generic parameters were ``, and // we've already decided that `T` is `Robin`, then we want to // search for a conformance `Robin : ISidekick`, which involved // apply the substitutions we already know... RefPtr solvedSubst = new GenericSubstitution(); solvedSubst->genericDecl = genericDeclRef.getDecl(); solvedSubst->outer = genericDeclRef.substitutions.substitutions; solvedSubst->args = args; resultSubst.substitutions = solvedSubst; for( auto constraintDecl : genericDeclRef.getDecl()->getMembersOfType() ) { DeclRef constraintDeclRef( constraintDecl, solvedSubst); // Extract the (substituted) sub- and super-type from the constraint. auto sub = GetSub(constraintDeclRef); auto sup = GetSup(constraintDeclRef); // Search for a witness that shows the constraint is satisfied. auto subTypeWitness = tryGetSubtypeWitness(sub, sup); if(subTypeWitness) { // We found a witness, so it will become an (implicit) argument. solvedSubst->args.Add(subTypeWitness); } else { // No witness was found, so the inference will now fail. // // TODO: Ideally we should print an error message in // this case, to let the user know why things failed. return SubstitutionSet(); } // TODO: We may need to mark some constrains in our constraint // system as being solved now, as a result of the witness we found. } // Make sure we haven't constructed any spurious constraints // that we aren't able to satisfy: for (auto c : system->constraints) { if (!c.satisfied) { return SubstitutionSet(); } } return resultSubst; } // State related to overload resolution for a call // to an overloaded symbol struct OverloadResolveContext { enum class Mode { // We are just checking if a candidate works or not JustTrying, // We want to actually update the AST for a chosen candidate ForReal, }; // Location to use when reporting overload-resolution errors. SourceLoc loc; // The original expression (if any) that triggered things RefPtr originalExpr; // Source location of the "function" part of the expression, if any SourceLoc funcLoc; // The original arguments to the call UInt argCount = 0; RefPtr* args = nullptr; RefPtr* argTypes = nullptr; UInt getArgCount() { return argCount; } RefPtr& getArg(UInt index) { return args[index]; } RefPtr& getArgType(UInt index) { if(argTypes) return argTypes[index]; else return getArg(index)->type.type; } bool disallowNestedConversions = false; RefPtr baseExpr; // Are we still trying out candidates, or are we // checking the chosen one for real? Mode mode = Mode::JustTrying; // We store one candidate directly, so that we don't // need to do dynamic allocation on the list every time OverloadCandidate bestCandidateStorage; OverloadCandidate* bestCandidate = nullptr; // Full list of all candidates being considered, in the ambiguous case List bestCandidates; }; struct ParamCounts { UInt required; UInt allowed; }; // count the number of parameters required/allowed for a callable ParamCounts CountParameters(FilteredMemberRefList params) { ParamCounts counts = { 0, 0 }; for (auto param : params) { counts.allowed++; // No initializer means no default value // // TODO(tfoley): The logic here is currently broken in two ways: // // 1. We are assuming that once one parameter has a default, then all do. // This can/should be validated earlier, so that we can assume it here. // // 2. We are not handling the possibility of multiple declarations for // a single function, where we'd need to merge default parameters across // all the declarations. if (!param.getDecl()->initExpr) { counts.required++; } } return counts; } // count the number of parameters required/allowed for a generic ParamCounts CountParameters(DeclRef genericRef) { ParamCounts counts = { 0, 0 }; for (auto m : genericRef.getDecl()->Members) { if (auto typeParam = m.As()) { counts.allowed++; if (!typeParam->initType.Ptr()) { counts.required++; } } else if (auto valParam = m.As()) { counts.allowed++; if (!valParam->initExpr) { counts.required++; } } } return counts; } bool TryCheckOverloadCandidateArity( OverloadResolveContext& context, OverloadCandidate const& candidate) { UInt argCount = context.getArgCount(); ParamCounts paramCounts = { 0, 0 }; switch (candidate.flavor) { case OverloadCandidate::Flavor::Func: paramCounts = CountParameters(GetParameters(candidate.item.declRef.As())); break; case OverloadCandidate::Flavor::Generic: paramCounts = CountParameters(candidate.item.declRef.As()); break; default: SLANG_UNEXPECTED("unknown flavor of overload candidate"); break; } if (argCount >= paramCounts.required && argCount <= paramCounts.allowed) return true; // Emit an error message if we are checking this call for real if (context.mode != OverloadResolveContext::Mode::JustTrying) { if (argCount < paramCounts.required) { getSink()->diagnose(context.loc, Diagnostics::notEnoughArguments, argCount, paramCounts.required); } else { SLANG_ASSERT(argCount > paramCounts.allowed); getSink()->diagnose(context.loc, Diagnostics::tooManyArguments, argCount, paramCounts.allowed); } } return false; } bool TryCheckOverloadCandidateFixity( OverloadResolveContext& context, OverloadCandidate const& candidate) { auto expr = context.originalExpr; auto decl = candidate.item.declRef.decl; if(auto prefixExpr = expr.As()) { if(decl->HasModifier()) return true; if (context.mode != OverloadResolveContext::Mode::JustTrying) { getSink()->diagnose(context.loc, Diagnostics::expectedPrefixOperator); getSink()->diagnose(decl, Diagnostics::seeDefinitionOf, decl->getName()); } return false; } else if(auto postfixExpr = expr.As()) { if(decl->HasModifier()) return true; if (context.mode != OverloadResolveContext::Mode::JustTrying) { getSink()->diagnose(context.loc, Diagnostics::expectedPostfixOperator); getSink()->diagnose(decl, Diagnostics::seeDefinitionOf, decl->getName()); } return false; } else { return true; } return false; } bool TryCheckGenericOverloadCandidateTypes( OverloadResolveContext& context, OverloadCandidate& candidate) { auto genericDeclRef = candidate.item.declRef.As(); // We will go ahead and hang onto the arguments that we've // already checked, since downstream validation might need // them. auto genSubst = new GenericSubstitution(); candidate.subst = genSubst; auto& checkedArgs = genSubst->args; int aa = 0; for (auto memberRef : getMembers(genericDeclRef)) { if (auto typeParamRef = memberRef.As()) { auto arg = context.getArg(aa++); TypeExp typeExp; if (context.mode == OverloadResolveContext::Mode::JustTrying) { typeExp = tryCoerceToProperType(TypeExp(arg)); if(!typeExp.type) { return false; } } else { typeExp = CoerceToProperType(TypeExp(arg)); } checkedArgs.Add(typeExp.type); } else if (auto valParamRef = memberRef.As()) { auto arg = context.getArg(aa++); if (context.mode == OverloadResolveContext::Mode::JustTrying) { ConversionCost cost = kConversionCost_None; if (!CanCoerce(GetType(valParamRef), arg->type, &cost)) { return false; } candidate.conversionCostSum += cost; } arg = Coerce(GetType(valParamRef), arg); auto val = ExtractGenericArgInteger(arg); checkedArgs.Add(val); } else { continue; } } // Okay, we've made it! return true; } bool TryCheckOverloadCandidateTypes( OverloadResolveContext& context, OverloadCandidate& candidate) { UInt argCount = context.getArgCount(); List> params; switch (candidate.flavor) { case OverloadCandidate::Flavor::Func: params = GetParameters(candidate.item.declRef.As()).ToArray(); break; case OverloadCandidate::Flavor::Generic: return TryCheckGenericOverloadCandidateTypes(context, candidate); default: SLANG_UNEXPECTED("unknown flavor of overload candidate"); break; } // Note(tfoley): We might have fewer arguments than parameters in the // case where one or more parameters had defaults. SLANG_RELEASE_ASSERT(argCount <= params.Count()); for (UInt ii = 0; ii < argCount; ++ii) { auto& arg = context.getArg(ii); auto argType = context.getArgType(ii); auto param = params[ii]; if (context.mode == OverloadResolveContext::Mode::JustTrying) { ConversionCost cost = kConversionCost_None; if( context.disallowNestedConversions ) { // We need an exact match in this case. if(!GetType(param)->Equals(argType)) return false; } else if (!CanCoerce(GetType(param), argType, &cost)) { return false; } candidate.conversionCostSum += cost; } else { arg = Coerce(GetType(param), arg); } } return true; } bool TryCheckOverloadCandidateDirections( OverloadResolveContext& /*context*/, OverloadCandidate const& /*candidate*/) { // TODO(tfoley): check `in` and `out` markers, as needed. return true; } // Create a witness that attests to the fact that `type` // is equal to itself. RefPtr createTypeEqualityWitness( Type* type) { RefPtr rs = new TypeEqualityWitness(); rs->sub = type; rs->sup = type; return rs; } // If `sub` is a subtype of `sup`, then return a value that // can serve as a "witness" for that fact. RefPtr tryGetSubtypeWitness( RefPtr sub, RefPtr sup) { if(sub->Equals(sup)) { // They are the same type, so we just need a witness // for type equality. return createTypeEqualityWitness(sub); } if(auto supDeclRefType = sup->As()) { auto supDeclRef = supDeclRefType->declRef; if(auto supInterfaceDeclRef = supDeclRef.As()) { if(auto witness = tryGetInterfaceConformanceWitness(sub, supInterfaceDeclRef)) { return witness; } } } return nullptr; } // In the case where we are explicitly applying a generic // to arguments (e.g., `G`) check that the constraints // on those parameters are satisfied. // // Note: the constraints actually work as additional parameters/arguments // of the generic, and so we need to reify them into the final // argument list. // bool TryCheckOverloadCandidateConstraints( OverloadResolveContext& context, OverloadCandidate const& candidate) { // We only need this step for generics, so always succeed on // everything else. if(candidate.flavor != OverloadCandidate::Flavor::Generic) return true; auto genericDeclRef = candidate.item.declRef.As(); SLANG_ASSERT(genericDeclRef); // otherwise we wouldn't be a generic candidate... // We should have the existing arguments to the generic // handy, so that we can construct a substitution list. RefPtr subst = candidate.subst.As(); SLANG_ASSERT(subst); subst->genericDecl = genericDeclRef.getDecl(); subst->outer = genericDeclRef.substitutions.substitutions; for( auto constraintDecl : genericDeclRef.getDecl()->getMembersOfType() ) { auto subset = genericDeclRef.substitutions; subset.substitutions = subst; DeclRef constraintDeclRef( constraintDecl, subset); auto sub = GetSub(constraintDeclRef); auto sup = GetSup(constraintDeclRef); auto subTypeWitness = tryGetSubtypeWitness(sub, sup); if(subTypeWitness) { subst->args.Add(subTypeWitness); } else { if(context.mode != OverloadResolveContext::Mode::JustTrying) { // TODO: diagnose a problem here getSink()->diagnose(context.loc, Diagnostics::unimplemented, "generic constraint not satisfied"); } return false; } } // Done checking all the constraints, hooray. return true; } // Try to check an overload candidate, but bail out // if any step fails void TryCheckOverloadCandidate( OverloadResolveContext& context, OverloadCandidate& candidate) { if (!TryCheckOverloadCandidateArity(context, candidate)) return; candidate.status = OverloadCandidate::Status::ArityChecked; if (!TryCheckOverloadCandidateFixity(context, candidate)) return; candidate.status = OverloadCandidate::Status::FixityChecked; if (!TryCheckOverloadCandidateTypes(context, candidate)) return; candidate.status = OverloadCandidate::Status::TypeChecked; if (!TryCheckOverloadCandidateDirections(context, candidate)) return; candidate.status = OverloadCandidate::Status::DirectionChecked; if (!TryCheckOverloadCandidateConstraints(context, candidate)) return; candidate.status = OverloadCandidate::Status::Appicable; } // Create the representation of a given generic applied to some arguments RefPtr createGenericDeclRef( RefPtr baseExpr, RefPtr originalExpr, RefPtr subst) { auto baseDeclRefExpr = baseExpr.As(); if (!baseDeclRefExpr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration"); return CreateErrorExpr(originalExpr); } auto baseGenericRef = baseDeclRefExpr->declRef.As(); if (!baseGenericRef) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration"); return CreateErrorExpr(originalExpr); } subst->genericDecl = baseGenericRef.getDecl(); subst->outer = baseGenericRef.substitutions.substitutions; DeclRef innerDeclRef(GetInner(baseGenericRef), subst); RefPtr base; if (auto mbrExpr = baseExpr.As()) base = mbrExpr->BaseExpression; return ConstructDeclRefExpr( innerDeclRef, base, originalExpr->loc); } // Take an overload candidate that previously got through // `TryCheckOverloadCandidate` above, and try to finish // up the work and turn it into a real expression. // // If the candidate isn't actually applicable, this is // where we'd start reporting the issue(s). RefPtr CompleteOverloadCandidate( OverloadResolveContext& context, OverloadCandidate& candidate) { // special case for generic argument inference failure if (candidate.status == OverloadCandidate::Status::GenericArgumentInferenceFailed) { String callString = getCallSignatureString(context); getSink()->diagnose( context.loc, Diagnostics::genericArgumentInferenceFailed, callString); String declString = getDeclSignatureString(candidate.item); getSink()->diagnose(candidate.item.declRef, Diagnostics::genericSignatureTried, declString); goto error; } context.mode = OverloadResolveContext::Mode::ForReal; if (!TryCheckOverloadCandidateArity(context, candidate)) goto error; if (!TryCheckOverloadCandidateFixity(context, candidate)) goto error; if (!TryCheckOverloadCandidateTypes(context, candidate)) goto error; if (!TryCheckOverloadCandidateDirections(context, candidate)) goto error; if (!TryCheckOverloadCandidateConstraints(context, candidate)) goto error; { auto baseExpr = ConstructLookupResultExpr( candidate.item, context.baseExpr, context.funcLoc); switch(candidate.flavor) { case OverloadCandidate::Flavor::Func: { RefPtr callExpr = context.originalExpr.As(); if(!callExpr) { callExpr = new InvokeExpr(); callExpr->loc = context.loc; for(UInt aa = 0; aa < context.argCount; ++aa) callExpr->Arguments.Add(context.getArg(aa)); } callExpr->FunctionExpr = baseExpr; callExpr->type = QualType(candidate.resultType); // A call may yield an l-value, and we should take a look at the candidate to be sure if(auto subscriptDeclRef = candidate.item.declRef.As()) { for(auto setter : subscriptDeclRef.getDecl()->getMembersOfType()) { callExpr->type.IsLeftValue = true; } for(auto refAccessor : subscriptDeclRef.getDecl()->getMembersOfType()) { callExpr->type.IsLeftValue = true; } } // TODO: there may be other cases that confer l-value-ness return callExpr; } break; case OverloadCandidate::Flavor::Generic: return createGenericDeclRef( baseExpr, context.originalExpr, candidate.subst.As()); break; default: SLANG_DIAGNOSE_UNEXPECTED(getSink(), context.loc, "unknown overload candidate flavor"); break; } } error: if(context.originalExpr) { return CreateErrorExpr(context.originalExpr.Ptr()); } else { SLANG_DIAGNOSE_UNEXPECTED(getSink(), context.loc, "no original expression for overload result"); return nullptr; } } // Implement a comparison operation between overload candidates, // so that the better candidate compares as less-than the other int CompareOverloadCandidates( OverloadCandidate* left, OverloadCandidate* right) { // If one candidate got further along in validation, pick it if (left->status != right->status) return int(right->status) - int(left->status); // If both candidates are applicable, then we need to compare // the costs of their type conversion sequences if(left->status == OverloadCandidate::Status::Appicable) { if (left->conversionCostSum != right->conversionCostSum) return left->conversionCostSum - right->conversionCostSum; } return 0; } void AddOverloadCandidateInner( OverloadResolveContext& context, OverloadCandidate& candidate) { // Filter our existing candidates, to remove any that are worse than our new one bool keepThisCandidate = true; // should this candidate be kept? if (context.bestCandidates.Count() != 0) { // We have multiple candidates right now, so filter them. bool anyFiltered = false; // Note that we are querying the list length on every iteration, // because we might remove things. for (UInt cc = 0; cc < context.bestCandidates.Count(); ++cc) { int cmp = CompareOverloadCandidates(&candidate, &context.bestCandidates[cc]); if (cmp < 0) { // our new candidate is better! // remove it from the list (by swapping in a later one) context.bestCandidates.FastRemoveAt(cc); // and then reduce our index so that we re-visit the same index --cc; anyFiltered = true; } else if(cmp > 0) { // our candidate is worse! keepThisCandidate = false; } } // It should not be possible that we removed some existing candidate *and* // chose not to keep this candidate (otherwise the better-ness relation // isn't transitive). Therefore we confirm that we either chose to keep // this candidate (in which case filtering is okay), or we didn't filter // anything. SLANG_ASSERT(keepThisCandidate || !anyFiltered); } else if(context.bestCandidate) { // There's only one candidate so far int cmp = CompareOverloadCandidates(&candidate, context.bestCandidate); if(cmp < 0) { // our new candidate is better! context.bestCandidate = nullptr; } else if (cmp > 0) { // our candidate is worse! keepThisCandidate = false; } } // If our candidate isn't good enough, then drop it if (!keepThisCandidate) return; // Otherwise we want to keep the candidate if (context.bestCandidates.Count() > 0) { // There were already multiple candidates, and we are adding one more context.bestCandidates.Add(candidate); } else if (context.bestCandidate) { // There was a unique best candidate, but now we are ambiguous context.bestCandidates.Add(*context.bestCandidate); context.bestCandidates.Add(candidate); context.bestCandidate = nullptr; } else { // This is the only candidate worthe keeping track of right now context.bestCandidateStorage = candidate; context.bestCandidate = &context.bestCandidateStorage; } } void AddOverloadCandidate( OverloadResolveContext& context, OverloadCandidate& candidate) { // Try the candidate out, to see if it is applicable at all. TryCheckOverloadCandidate(context, candidate); // Now (potentially) add it to the set of candidate overloads to consider. AddOverloadCandidateInner(context, candidate); } void AddFuncOverloadCandidate( LookupResultItem item, DeclRef funcDeclRef, OverloadResolveContext& context) { auto funcDecl = funcDeclRef.getDecl(); checkDecl(funcDecl); // If this function is a redeclaration, // then we don't want to include it multiple times, // and mistakenly think we have an ambiguous call. // // Instead, we will carefully consider only the // "primary" declaration of any callable. if (auto primaryDecl = funcDecl->primaryDecl) { if (funcDecl != primaryDecl) { // This is a redeclaration, so we don't // want to consider it. The primary // declaration should also get considered // for the call site and it will match // anything this declaration would have // matched. return; } } OverloadCandidate candidate; candidate.flavor = OverloadCandidate::Flavor::Func; candidate.item = item; candidate.resultType = GetResultType(funcDeclRef); AddOverloadCandidate(context, candidate); } void AddFuncOverloadCandidate( RefPtr /*funcType*/, OverloadResolveContext& /*context*/) { #if 0 if (funcType->decl) { AddFuncOverloadCandidate(funcType->decl, context); } else if (funcType->Func) { AddFuncOverloadCandidate(funcType->Func->SyntaxNode, context); } else if (funcType->Component) { AddComponentFuncOverloadCandidate(funcType->Component, context); } #else throw "unimplemented"; #endif } // Add a candidate callee for overload resolution, based on // calling a particular `ConstructorDecl`. void AddCtorOverloadCandidate( LookupResultItem typeItem, RefPtr type, DeclRef ctorDeclRef, OverloadResolveContext& context, RefPtr resultType) { checkDecl(ctorDeclRef.getDecl()); // `typeItem` refers to the type being constructed (the thing // that was applied as a function) so we need to construct // a `LookupResultItem` that refers to the constructor instead LookupResultItem ctorItem; ctorItem.declRef = ctorDeclRef; ctorItem.breadcrumbs = new LookupResultItem::Breadcrumb(LookupResultItem::Breadcrumb::Kind::Member, typeItem.declRef, typeItem.breadcrumbs); OverloadCandidate candidate; candidate.flavor = OverloadCandidate::Flavor::Func; candidate.item = ctorItem; candidate.resultType = resultType; AddOverloadCandidate(context, candidate); } // If the given declaration has generic parameters, then // return the corresponding `GenericDecl` that holds the // parameters, etc. GenericDecl* GetOuterGeneric(Decl* decl) { auto parentDecl = decl->ParentDecl; if (!parentDecl) return nullptr; auto parentGeneric = dynamic_cast(parentDecl); return parentGeneric; } // Try to find a unification for two values bool TryUnifyVals( ConstraintSystem& constraints, RefPtr fst, RefPtr snd) { // if both values are types, then unify types if (auto fstType = fst.As()) { if (auto sndType = snd.As()) { return TryUnifyTypes(constraints, fstType, sndType); } } // if both values are constant integers, then compare them if (auto fstIntVal = fst.As()) { if (auto sndIntVal = snd.As()) { return fstIntVal->value == sndIntVal->value; } } // Check if both are integer values in general if (auto fstInt = fst.As()) { if (auto sndInt = snd.As()) { auto fstParam = fstInt.As(); auto sndParam = sndInt.As(); bool okay = false; if (fstParam) { if(TryUnifyIntParam(constraints, fstParam->declRef, sndInt)) okay = true; } if (sndParam) { if(TryUnifyIntParam(constraints, sndParam->declRef, fstInt)) okay = true; } return okay; } } if (auto fstWit = fst.As()) { if (auto sndWit = snd.As()) { auto constraintDecl1 = fstWit->declRef.As(); auto constraintDecl2 = sndWit->declRef.As(); SLANG_ASSERT(constraintDecl1); SLANG_ASSERT(constraintDecl2); return TryUnifyTypes(constraints, constraintDecl1.getDecl()->getSup().type, constraintDecl2.getDecl()->getSup().type); } } SLANG_UNIMPLEMENTED_X("value unification case"); // default: fail return false; } bool tryUnifySubstitutions( ConstraintSystem& constraints, RefPtr fst, RefPtr snd) { // They must both be NULL or non-NULL if (!fst || !snd) return !fst && !snd; if(auto fstGeneric = fst.As()) { if(auto sndGeneric = snd.As()) { return tryUnifyGenericSubstitutions( constraints, fstGeneric, sndGeneric); } } // TODO: need to handle other cases here return false; } bool tryUnifyGenericSubstitutions( ConstraintSystem& constraints, RefPtr fst, RefPtr snd) { SLANG_ASSERT(fst); SLANG_ASSERT(snd); auto fstGen = fst; auto sndGen = snd; // They must be specializing the same generic if (fstGen->genericDecl != sndGen->genericDecl) return false; // Their arguments must unify SLANG_RELEASE_ASSERT(fstGen->args.Count() == sndGen->args.Count()); UInt argCount = fstGen->args.Count(); bool okay = true; for (UInt aa = 0; aa < argCount; ++aa) { if (!TryUnifyVals(constraints, fstGen->args[aa], sndGen->args[aa])) { okay = false; } } // Their "base" specializations must unify if (!tryUnifySubstitutions(constraints, fstGen->outer, sndGen->outer)) { okay = false; } return okay; } bool TryUnifyTypeParam( ConstraintSystem& constraints, RefPtr typeParamDecl, RefPtr type) { // We want to constrain the given type parameter // to equal the given type. Constraint constraint; constraint.decl = typeParamDecl.Ptr(); constraint.val = type; constraints.constraints.Add(constraint); return true; } bool TryUnifyIntParam( ConstraintSystem& constraints, RefPtr paramDecl, RefPtr val) { // We only want to accumulate constraints on // the parameters of the declarations being // specialized (don't accidentially constrain // parameters of a generic function based on // calls in its body). if(paramDecl->ParentDecl != constraints.genericDecl) return false; // We want to constrain the given parameter to equal the given value. Constraint constraint; constraint.decl = paramDecl.Ptr(); constraint.val = val; constraints.constraints.Add(constraint); return true; } bool TryUnifyIntParam( ConstraintSystem& constraints, DeclRef const& varRef, RefPtr val) { if(auto genericValueParamRef = varRef.As()) { return TryUnifyIntParam(constraints, RefPtr(genericValueParamRef.getDecl()), val); } else { return false; } } bool TryUnifyTypesByStructuralMatch( ConstraintSystem& constraints, RefPtr fst, RefPtr snd) { if (auto fstDeclRefType = fst->As()) { auto fstDeclRef = fstDeclRefType->declRef; if (auto typeParamDecl = dynamic_cast(fstDeclRef.getDecl())) return TryUnifyTypeParam(constraints, typeParamDecl, snd); if (auto sndDeclRefType = snd->As()) { auto sndDeclRef = sndDeclRefType->declRef; if (auto typeParamDecl = dynamic_cast(sndDeclRef.getDecl())) return TryUnifyTypeParam(constraints, typeParamDecl, fst); // can't be unified if they refer to differnt declarations. if (fstDeclRef.getDecl() != sndDeclRef.getDecl()) return false; // next we need to unify the substitutions applied // to each decalration reference. if (!tryUnifySubstitutions( constraints, fstDeclRef.substitutions.substitutions, sndDeclRef.substitutions.substitutions)) { return false; } return true; } } return false; } bool TryUnifyTypes( ConstraintSystem& constraints, RefPtr fst, RefPtr snd) { if (fst->Equals(snd)) return true; // An error type can unify with anything, just so we avoid cascading errors. if (auto fstErrorType = fst->As()) return true; if (auto sndErrorType = snd->As()) return true; // A generic parameter type can unify with anything. // TODO: there actually needs to be some kind of "occurs check" sort // of thing here... if (auto fstDeclRefType = fst->As()) { auto fstDeclRef = fstDeclRefType->declRef; if (auto typeParamDecl = dynamic_cast(fstDeclRef.getDecl())) { if(typeParamDecl->ParentDecl == constraints.genericDecl ) return TryUnifyTypeParam(constraints, typeParamDecl, snd); } } if (auto sndDeclRefType = snd->As()) { auto sndDeclRef = sndDeclRefType->declRef; if (auto typeParamDecl = dynamic_cast(sndDeclRef.getDecl())) { if(typeParamDecl->ParentDecl == constraints.genericDecl ) return TryUnifyTypeParam(constraints, typeParamDecl, fst); } } // If we can unify the types structurally, then we are golden if(TryUnifyTypesByStructuralMatch(constraints, fst, snd)) return true; // Now we need to consider cases where coercion might // need to be applied. For now we can try to do this // in a completely ad hoc fashion, but eventually we'd // want to do it more formally. if(auto fstVectorType = fst->As()) { if(auto sndScalarType = snd->As()) { return TryUnifyTypes( constraints, fstVectorType->elementType, sndScalarType); } } if(auto fstScalarType = fst->As()) { if(auto sndVectorType = snd->As()) { return TryUnifyTypes( constraints, fstScalarType, sndVectorType->elementType); } } // TODO: the same thing for vectors... return false; } // Is the candidate extension declaration actually applicable to the given type DeclRef ApplyExtensionToType( ExtensionDecl* extDecl, RefPtr type) { DeclRef extDeclRef = makeDeclRef(extDecl); // If the extension is a generic extension, then we // need to infer type argumenst that will give // us a target type that matches `type`. // if (auto extGenericDecl = GetOuterGeneric(extDecl)) { ConstraintSystem constraints; constraints.loc = extDecl->loc; constraints.genericDecl = extGenericDecl; if (!TryUnifyTypes(constraints, extDecl->targetType.Ptr(), type)) return DeclRef(); auto constraintSubst = TrySolveConstraintSystem(&constraints, DeclRef(extGenericDecl, nullptr).As()); if (!constraintSubst) { return DeclRef(); } // Consruct a reference to the extension with our constraint variables // set as they were found by solving the constraint system. extDeclRef = DeclRef(extDecl, constraintSubst).As(); } // Now extract the target type from our (possibly specialized) extension decl-ref. RefPtr targetType = GetTargetType(extDeclRef); // As a bit of a kludge here, if the target type of the extension is // an interface, and the `type` we are trying to match up has a this-type // substitution for that interface, then we want to attach a matching // substitution to the extension decl-ref. if(auto targetDeclRefType = targetType->As()) { if(auto targetInterfaceDeclRef = targetDeclRefType->declRef.As()) { // Okay, the target type is an interface. // // Is the type we want to apply to also an interface? if(auto appDeclRefType = type->As()) { if(auto appInterfaceDeclRef = appDeclRefType->declRef.As()) { if(appInterfaceDeclRef.getDecl() == targetInterfaceDeclRef.getDecl()) { // Looks like we have a match in the types, // now let's see if we have a this-type substitution. if(auto appThisTypeSubst = appInterfaceDeclRef.substitutions.substitutions.As()) { if(appThisTypeSubst->interfaceDecl == appInterfaceDeclRef.getDecl()) { // The type we want to apply to has a this-type substitution, // and (by construction) the target type currently does not. // SLANG_ASSERT(!targetInterfaceDeclRef.substitutions.substitutions.As()); // We will create a new substitution to apply to the target type. RefPtr newTargetSubst = new ThisTypeSubstitution(); newTargetSubst->interfaceDecl = appThisTypeSubst->interfaceDecl; newTargetSubst->witness = appThisTypeSubst->witness; newTargetSubst->outer = targetInterfaceDeclRef.substitutions.substitutions; targetType = DeclRefType::Create(getSession(), DeclRef(targetInterfaceDeclRef.getDecl(), newTargetSubst)); // Note: we are constructing a this-type substitution that // we will apply to the extension declaration as well. // This is not strictly allowed by our current representation // choices, but we need it in order to make sure that // references to the target type of the extension // declaration have a chance to resolve the way we want them to. RefPtr newExtSubst = new ThisTypeSubstitution(); newExtSubst->interfaceDecl = appThisTypeSubst->interfaceDecl; newExtSubst->witness = appThisTypeSubst->witness; newExtSubst->outer = extDeclRef.substitutions.substitutions; extDeclRef = DeclRef( extDeclRef.getDecl(), newExtSubst); // TODO: Ideally we should also apply the chosen specialization to // the decl-ref for the extension, so that subsequent lookup through // the members of this extension will retain that substitution and // be able to apply it. // // E.g., if an extension method returns a value of an associated // type, then we'd want that to become specialized to a concrete // type when using the extension method on a value of concrete type. // // The challenge here that makes me reluctant to just staple on // such a substitution is that it wouldn't follow our implicit // rules about where `ThisTypeSubstitution`s can appear. } } } } } } } // In order for this extension to apply to the given type, we // need to have a match on the target types. if (!type->Equals(targetType)) return DeclRef(); return extDeclRef; } #if 0 bool TryUnifyArgAndParamTypes( ConstraintSystem& system, RefPtr argExpr, DeclRef paramDeclRef) { // TODO(tfoley): potentially need a bit more // nuance in case where argument might be // an overload group... return TryUnifyTypes(system, argExpr->type, GetType(paramDeclRef)); } #endif // Take a generic declaration and try to specialize its parameters // so that the resulting inner declaration can be applicable in // a particular context... DeclRef SpecializeGenericForOverload( DeclRef genericDeclRef, OverloadResolveContext& context) { checkDecl(genericDeclRef.getDecl()); ConstraintSystem constraints; constraints.loc = context.loc; constraints.genericDecl = genericDeclRef.getDecl(); // Construct a reference to the inner declaration that has any generic // parameter substitutions in place already, but *not* any substutions // for the generic declaration we are currently trying to infer. auto innerDecl = GetInner(genericDeclRef); DeclRef unspecializedInnerRef = DeclRef(innerDecl, genericDeclRef.substitutions); // Check what type of declaration we are dealing with, and then try // to match it up with the arguments accordingly... if (auto funcDeclRef = unspecializedInnerRef.As()) { auto params = GetParameters(funcDeclRef).ToArray(); UInt argCount = context.getArgCount(); UInt paramCount = params.Count(); // Bail out on mismatch. // TODO(tfoley): need more nuance here if (argCount != paramCount) { return DeclRef(nullptr, nullptr); } for (UInt aa = 0; aa < argCount; ++aa) { #if 0 if (!TryUnifyArgAndParamTypes(constraints, args[aa], params[aa])) return DeclRef(nullptr, nullptr); #else // The question here is whether failure to "unify" an argument // and parameter should lead to immediate failure. // // The case that is interesting is if we want to unify, say: // `vector` and `vector` // // It is clear that we should solve with `N = 3`, and then // a later step may find that the resulting types aren't // actually a match. // // A more refined approach to "unification" could of course // see that `int` can convert to `float` and use that fact. // (and indeed we already use something like this to unify // `float` and `vector`) // // So the question is then whether a mismatch during the // unification step should be taken as an immediate failure... TryUnifyTypes(constraints, context.getArgType(aa), GetType(params[aa])); #endif } } else { // TODO(tfoley): any other cases needed here? return DeclRef(nullptr, nullptr); } auto constraintSubst = TrySolveConstraintSystem(&constraints, genericDeclRef); if (!constraintSubst) { // constraint solving failed return DeclRef(nullptr, nullptr); } // We can now construct a reference to the inner declaration using // the solution to our constraints. return DeclRef(innerDecl, constraintSubst); } void AddAggTypeOverloadCandidates( LookupResultItem typeItem, RefPtr type, DeclRef aggTypeDeclRef, OverloadResolveContext& context, RefPtr resultType) { for (auto ctorDeclRef : getMembersOfType(aggTypeDeclRef)) { // now work through this candidate... AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context, resultType); } // Also check for generic constructors. // // TODO: There is way too much duplication between this case and the extension // handling below, and all of this is *also* duplicative with the ordinary // overload resolution logic for function. // // The right solution is to handle a "constructor" call expression by // first doing member lookup in the type (for initializer members, which // should all share a common name), and then to do overload resolution using // the (possibly overloaded) result of that lookup. // for (auto genericDeclRef : getMembersOfType(aggTypeDeclRef)) { if (auto ctorDecl = genericDeclRef.getDecl()->inner.As()) { DeclRef innerRef = SpecializeGenericForOverload(genericDeclRef, context); if (!innerRef) continue; DeclRef innerCtorRef = innerRef.As(); AddCtorOverloadCandidate(typeItem, type, innerCtorRef, context, resultType); } } // Now walk through any extensions we can find for this types for (auto ext = GetCandidateExtensions(aggTypeDeclRef); ext; ext = ext->nextCandidateExtension) { auto extDeclRef = ApplyExtensionToType(ext, type); if (!extDeclRef) continue; for (auto ctorDeclRef : getMembersOfType(extDeclRef)) { // TODO(tfoley): `typeItem` here should really reference the extension... // now work through this candidate... AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context, resultType); } // Also check for generic constructors for (auto genericDeclRef : getMembersOfType(extDeclRef)) { if (auto ctorDecl = genericDeclRef.getDecl()->inner.As()) { DeclRef innerRef = SpecializeGenericForOverload(genericDeclRef, context); if (!innerRef) continue; DeclRef innerCtorRef = innerRef.As(); AddCtorOverloadCandidate(typeItem, type, innerCtorRef, context, resultType); // TODO(tfoley): need a way to do the solving step for the constraint system } } } } void addGenericTypeParamOverloadCandidates( DeclRef typeDeclRef, OverloadResolveContext& context, RefPtr resultType) { // We need to look for any constraints placed on the generic // type parameter, since they will give us information on // interfaces that the type must conform to. // We expect the parent of the generic type parameter to be a generic... auto genericDeclRef = typeDeclRef.GetParent().As(); SLANG_ASSERT(genericDeclRef); for(auto constraintDeclRef : getMembersOfType(genericDeclRef)) { // Does this constraint pertain to the type we are working on? // // We want constraints of the form `T : Foo` where `T` is the // generic parameter in question, and `Foo` is whatever we are // constraining it to. auto subType = GetSub(constraintDeclRef); auto subDeclRefType = subType->As(); if(!subDeclRefType) continue; if(!subDeclRefType->declRef.Equals(typeDeclRef)) continue; // The super-type in the constraint (e.g., `Foo` in `T : Foo`) // will tell us a type we should use for lookup. auto bound = GetSup(constraintDeclRef); // Go ahead and use the target type: // // TODO: Need to consider case where this might recurse infinitely. AddTypeOverloadCandidates(bound, context, resultType); } } void AddTypeOverloadCandidates( RefPtr type, OverloadResolveContext& context, RefPtr resultType) { if (auto declRefType = type->As()) { auto declRef = declRefType->declRef; if (auto aggTypeDeclRef = declRef.As()) { AddAggTypeOverloadCandidates(LookupResultItem(aggTypeDeclRef), type, aggTypeDeclRef, context, resultType); } else if(auto genericTypeParamDeclRef = declRef.As()) { addGenericTypeParamOverloadCandidates( genericTypeParamDeclRef, context, resultType); } } } void AddDeclRefOverloadCandidates( LookupResultItem item, OverloadResolveContext& context) { auto declRef = item.declRef; if (auto funcDeclRef = item.declRef.As()) { AddFuncOverloadCandidate(item, funcDeclRef, context); } else if (auto aggTypeDeclRef = item.declRef.As()) { auto type = DeclRefType::Create( getSession(), aggTypeDeclRef); AddAggTypeOverloadCandidates(item, type, aggTypeDeclRef, context, type); } else if (auto genericDeclRef = item.declRef.As()) { // Try to infer generic arguments, based on the context DeclRef innerRef = SpecializeGenericForOverload(genericDeclRef, context); if (innerRef) { // If inference works, then we've now got a // specialized declaration reference we can apply. LookupResultItem innerItem; innerItem.breadcrumbs = item.breadcrumbs; innerItem.declRef = innerRef; AddDeclRefOverloadCandidates(innerItem, context); } else { // If inference failed, then we need to create // a candidate that can be used to reflect that fact // (so we can report a good error) OverloadCandidate candidate; candidate.item = item; candidate.flavor = OverloadCandidate::Flavor::UnspecializedGeneric; candidate.status = OverloadCandidate::Status::GenericArgumentInferenceFailed; AddOverloadCandidateInner(context, candidate); } } else if( auto typeDefDeclRef = item.declRef.As() ) { auto type = getNamedType(getSession(), typeDefDeclRef); AddTypeOverloadCandidates(GetType(typeDefDeclRef), context, type); } else if( auto genericTypeParamDeclRef = item.declRef.As() ) { auto type = DeclRefType::Create( getSession(), genericTypeParamDeclRef); addGenericTypeParamOverloadCandidates(genericTypeParamDeclRef, context, type); } else { // TODO(tfoley): any other cases needed here? } } void AddOverloadCandidates( RefPtr funcExpr, OverloadResolveContext& context) { auto funcExprType = funcExpr->type; if (auto declRefExpr = funcExpr.As()) { // The expression directly referenced a declaration, // so we can use that declaration directly to look // for anything applicable. AddDeclRefOverloadCandidates(LookupResultItem(declRefExpr->declRef), context); } else if (auto funcType = funcExprType->As()) { // TODO(tfoley): deprecate this path... AddFuncOverloadCandidate(funcType, context); } else if (auto overloadedExpr = funcExpr.As()) { auto lookupResult = overloadedExpr->lookupResult2; SLANG_RELEASE_ASSERT(lookupResult.isOverloaded()); for(auto item : lookupResult.items) { AddDeclRefOverloadCandidates(item, context); } } else if (auto overloadedExpr2 = funcExpr.As()) { for (auto item : overloadedExpr2->candidiateExprs) { AddOverloadCandidates(item, context); } } else if (auto typeType = funcExprType->As()) { // If none of the above cases matched, but we are // looking at a type, then I suppose we have // a constructor call on our hands. // // TODO(tfoley): are there any meaningful types left // that aren't declaration references? auto type = typeType->type; AddTypeOverloadCandidates(type, context, type); return; } } void formatType(StringBuilder& sb, RefPtr type) { sb << type->ToString(); } void formatVal(StringBuilder& sb, RefPtr val) { sb << val->ToString(); } void formatDeclPath(StringBuilder& sb, DeclRef declRef) { // Find the parent declaration auto parentDeclRef = declRef.GetParent(); // If the immediate parent is a generic, then we probably // want the declaration above that... auto parentGenericDeclRef = parentDeclRef.As(); if(parentGenericDeclRef) { parentDeclRef = parentGenericDeclRef.GetParent(); } // Depending on what the parent is, we may want to format things specially if(auto aggTypeDeclRef = parentDeclRef.As()) { formatDeclPath(sb, aggTypeDeclRef); sb << "."; } sb << getText(declRef.GetName()); // If the parent declaration is a generic, then we need to print out its // signature if( parentGenericDeclRef ) { auto genSubst = declRef.substitutions.substitutions.As(); SLANG_RELEASE_ASSERT(genSubst); SLANG_RELEASE_ASSERT(genSubst->genericDecl == parentGenericDeclRef.getDecl()); sb << "<"; bool first = true; for(auto arg : genSubst->args) { if(!first) sb << ", "; formatVal(sb, arg); first = false; } sb << ">"; } } void formatDeclParams(StringBuilder& sb, DeclRef declRef) { if (auto funcDeclRef = declRef.As()) { // This is something callable, so we need to also print parameter types for overloading sb << "("; bool first = true; for (auto paramDeclRef : GetParameters(funcDeclRef)) { if (!first) sb << ", "; formatType(sb, GetType(paramDeclRef)); first = false; } sb << ")"; } else if(auto genericDeclRef = declRef.As()) { sb << "<"; bool first = true; for (auto paramDeclRef : getMembers(genericDeclRef)) { if(auto genericTypeParam = paramDeclRef.As()) { if (!first) sb << ", "; first = false; sb << getText(genericTypeParam.GetName()); } else if(auto genericValParam = paramDeclRef.As()) { if (!first) sb << ", "; first = false; formatType(sb, GetType(genericValParam)); sb << " "; sb << getText(genericValParam.GetName()); } else {} } sb << ">"; formatDeclParams(sb, DeclRef(GetInner(genericDeclRef), genericDeclRef.substitutions)); } else { } } void formatDeclSignature(StringBuilder& sb, DeclRef declRef) { formatDeclPath(sb, declRef); formatDeclParams(sb, declRef); } String getDeclSignatureString(DeclRef declRef) { StringBuilder sb; formatDeclSignature(sb, declRef); return sb.ProduceString(); } String getDeclSignatureString(LookupResultItem item) { return getDeclSignatureString(item.declRef); } String getCallSignatureString( OverloadResolveContext& context) { StringBuilder argsListBuilder; argsListBuilder << "("; UInt argCount = context.getArgCount(); for( UInt aa = 0; aa < argCount; ++aa ) { if(aa != 0) argsListBuilder << ", "; argsListBuilder << context.getArgType(aa)->ToString(); } argsListBuilder << ")"; return argsListBuilder.ProduceString(); } #if 0 String GetCallSignatureString(RefPtr expr) { return getCallSignatureString(expr->Arguments); } #endif RefPtr ResolveInvoke(InvokeExpr * expr) { OverloadResolveContext context; // check if this is a stdlib operator call, if so we want to use cached results // to speed up compilation bool shouldAddToCache = false; OperatorOverloadCacheKey key; TypeCheckingCache* typeCheckingCache = getSession()->getTypeCheckingCache(); if (auto opExpr = expr->As()) { if (key.fromOperatorExpr(opExpr)) { OverloadCandidate candidate; if (typeCheckingCache->resolvedOperatorOverloadCache.TryGetValue(key, candidate)) { context.bestCandidateStorage = candidate; context.bestCandidate = &context.bestCandidateStorage; } else { shouldAddToCache = true; } } } // Look at the base expression for the call, and figure out how to invoke it. auto funcExpr = expr->FunctionExpr; auto funcExprType = funcExpr->type; // If we are trying to apply an erroroneous expression, then just bail out now. if(IsErrorExpr(funcExpr)) { return CreateErrorExpr(expr); } // If any of the arguments is an error, then we should bail out, to avoid // cascading errors where we successfully pick an overload, but not the one // the user meant. for (auto arg : expr->Arguments) { if (IsErrorExpr(arg)) return CreateErrorExpr(expr); } context.originalExpr = expr; context.funcLoc = funcExpr->loc; context.argCount = expr->Arguments.Count(); context.args = expr->Arguments.Buffer(); context.loc = expr->loc; if (auto funcMemberExpr = funcExpr.As()) { context.baseExpr = funcMemberExpr->BaseExpression; } else if (auto funcOverloadExpr = funcExpr.As()) { context.baseExpr = funcOverloadExpr->base; } else if (auto funcOverloadExpr2 = funcExpr.As()) { context.baseExpr = funcOverloadExpr2->base; } if (!context.bestCandidate) { AddOverloadCandidates(funcExpr, context); } if (context.bestCandidates.Count() > 0) { // Things were ambiguous. // It might be that things were only ambiguous because // one of the argument expressions had an error, and // so a bunch of candidates could match at that position. // // If any argument was an error, we skip out on printing // another message, to avoid cascading errors. for (auto arg : expr->Arguments) { if (IsErrorExpr(arg)) { return CreateErrorExpr(expr); } } Name* funcName = nullptr; if (auto baseVar = funcExpr.As()) funcName = baseVar->name; else if(auto baseMemberRef = funcExpr.As()) funcName = baseMemberRef->name; String argsList = getCallSignatureString(context); if (context.bestCandidates[0].status != OverloadCandidate::Status::Appicable) { // There were multple equally-good candidates, but none actually usable. // We will construct a diagnostic message to help out. if (funcName) { getSink()->diagnose(expr, Diagnostics::noApplicableOverloadForNameWithArgs, funcName, argsList); } else { getSink()->diagnose(expr, Diagnostics::noApplicableWithArgs, argsList); } } else { // There were multiple applicable candidates, so we need to report them. if (funcName) { getSink()->diagnose(expr, Diagnostics::ambiguousOverloadForNameWithArgs, funcName, argsList); } else { getSink()->diagnose(expr, Diagnostics::ambiguousOverloadWithArgs, argsList); } } { UInt candidateCount = context.bestCandidates.Count(); UInt maxCandidatesToPrint = 10; // don't show too many candidates at once... UInt candidateIndex = 0; for (auto candidate : context.bestCandidates) { String declString = getDeclSignatureString(candidate.item); // declString = declString + "[" + String(candidate.conversionCostSum) + "]"; #if 0 // Debugging: ensure that we don't consider multiple declarations of the same operation if (auto decl = dynamic_cast(candidate.item.declRef.decl)) { char buffer[1024]; sprintf_s(buffer, sizeof(buffer), "[this:%p, primary:%p, next:%p]", decl, decl->primaryDecl, decl->nextDecl); declString.append(buffer); } #endif getSink()->diagnose(candidate.item.declRef, Diagnostics::overloadCandidate, declString); candidateIndex++; if (candidateIndex == maxCandidatesToPrint) break; } if (candidateIndex != candidateCount) { getSink()->diagnose(expr, Diagnostics::moreOverloadCandidates, candidateCount - candidateIndex); } } return CreateErrorExpr(expr); } else if (context.bestCandidate) { // There was one best candidate, even if it might not have been // applicable in the end. // We will report errors for this one candidate, then, to give // the user the most help we can. if (shouldAddToCache) typeCheckingCache->resolvedOperatorOverloadCache[key] = *context.bestCandidate; return CompleteOverloadCandidate(context, *context.bestCandidate); } else { // Nothing at all was found that we could even consider invoking getSink()->diagnose(expr->FunctionExpr, Diagnostics::expectedFunction); expr->type = QualType(getSession()->getErrorType()); return expr; } } void AddGenericOverloadCandidate( LookupResultItem baseItem, OverloadResolveContext& context) { if (auto genericDeclRef = baseItem.declRef.As()) { checkDecl(genericDeclRef.getDecl()); OverloadCandidate candidate; candidate.flavor = OverloadCandidate::Flavor::Generic; candidate.item = baseItem; candidate.resultType = nullptr; AddOverloadCandidate(context, candidate); } } void AddGenericOverloadCandidates( RefPtr baseExpr, OverloadResolveContext& context) { if(auto baseDeclRefExpr = baseExpr.As()) { auto declRef = baseDeclRefExpr->declRef; AddGenericOverloadCandidate(LookupResultItem(declRef), context); } else if (auto overloadedExpr = baseExpr.As()) { // We are referring to a bunch of declarations, each of which might be generic LookupResult result; for (auto item : overloadedExpr->lookupResult2.items) { AddGenericOverloadCandidate(item, context); } } else { // any other cases? } } RefPtr visitGenericAppExpr(GenericAppExpr * genericAppExpr) { // We are applying a generic to arguments, but there might be multiple generic // declarations with the same name, so this becomes a specialized case of // overload resolution. // Start by checking the base expression and arguments. auto& baseExpr = genericAppExpr->FunctionExpr; baseExpr = CheckTerm(baseExpr); auto& args = genericAppExpr->Arguments; for (auto& arg : args) { arg = CheckTerm(arg); } // If there was an error in the base expression, or in any of // the arguments, then just bail. if (IsErrorExpr(baseExpr)) { return CreateErrorExpr(genericAppExpr); } for (auto argExpr : args) { if (IsErrorExpr(argExpr)) { return CreateErrorExpr(genericAppExpr); } } // Otherwise, let's start looking at how to find an overload... OverloadResolveContext context; context.originalExpr = genericAppExpr; context.funcLoc = baseExpr->loc; context.argCount = args.Count(); context.args = args.Buffer(); context.loc = genericAppExpr->loc; context.baseExpr = GetBaseExpr(baseExpr); AddGenericOverloadCandidates(baseExpr, context); if (context.bestCandidates.Count() > 0) { // Things were ambiguous. if (context.bestCandidates[0].status != OverloadCandidate::Status::Appicable) { // There were multple equally-good candidates, but none actually usable. // We will construct a diagnostic message to help out. // TODO(tfoley): print a reasonable message here... getSink()->diagnose(genericAppExpr, Diagnostics::unimplemented, "no applicable generic"); return CreateErrorExpr(genericAppExpr); } else { // There were multiple viable candidates, but that isn't an error: we just need // to complete all of them and create an overloaded expression as a result. auto overloadedExpr = new OverloadedExpr2(); overloadedExpr->base = context.baseExpr; for (auto candidate : context.bestCandidates) { auto candidateExpr = CompleteOverloadCandidate(context, candidate); overloadedExpr->candidiateExprs.Add(candidateExpr); } return overloadedExpr; } } else if (context.bestCandidate) { // There was one best candidate, even if it might not have been // applicable in the end. // We will report errors for this one candidate, then, to give // the user the most help we can. return CompleteOverloadCandidate(context, *context.bestCandidate); } else { // Nothing at all was found that we could even consider invoking getSink()->diagnose(genericAppExpr, Diagnostics::unimplemented, "expected a generic"); return CreateErrorExpr(genericAppExpr); } } RefPtr visitSharedTypeExpr(SharedTypeExpr* expr) { if (!expr->type.Ptr()) { expr->base = CheckProperType(expr->base); expr->type = expr->base.exp->type; } return expr; } RefPtr CheckExpr(RefPtr expr) { auto term = CheckTerm(expr); // TODO(tfoley): Need a step here to ensure that the term actually // resolves to a (single) expression with a real type. return term; } RefPtr CheckInvokeExprWithCheckedOperands(InvokeExpr *expr) { auto rs = ResolveInvoke(expr); if (auto invoke = dynamic_cast(rs.Ptr())) { // if this is still an invoke expression, test arguments passed to inout/out parameter are LValues if(auto funcType = invoke->FunctionExpr->type->As()) { UInt paramCount = funcType->getParamCount(); for (UInt pp = 0; pp < paramCount; ++pp) { auto paramType = funcType->getParamType(pp); if (paramType->As() || paramType->As()) { // `out`, `inout`, and `ref` parameters currently require // an *exact* match on the type of the argument. // // TODO: relax this requirement by allowing an argument // for an `inout` parameter to be converted in both // directions. // if( pp < expr->Arguments.Count() ) { auto argExpr = expr->Arguments[pp]; if( !argExpr->type.IsLeftValue ) { getSink()->diagnose( argExpr, Diagnostics::argumentExpectedLValue, pp); if( auto implicitCastExpr = argExpr.As() ) { getSink()->diagnose( argExpr, Diagnostics::implicitCastUsedAsLValue, implicitCastExpr->Arguments[0]->type, implicitCastExpr->type); } } } else { // This implies that the function had an `out` // or `inout` parameter and they gave it a default // argument expression. I'm not even sure what // that would mean. // // TODO: make sure this gets validated on the // declaring side. // SLANG_DIAGNOSE_UNEXPECTED(getSink(), invoke, "default argument expression for out/inout paameter"); } } } } } return rs; } RefPtr visitInvokeExpr(InvokeExpr *expr) { // check the base expression first expr->FunctionExpr = CheckExpr(expr->FunctionExpr); // Next check the argument expressions for (auto & arg : expr->Arguments) { arg = CheckExpr(arg); } return CheckInvokeExprWithCheckedOperands(expr); } RefPtr visitVarExpr(VarExpr *expr) { // If we've already resolved this expression, don't try again. if (expr->declRef) return expr; expr->type = QualType(getSession()->getErrorType()); auto lookupResult = lookUp( getSession(), this, expr->name, expr->scope); if (lookupResult.isValid()) { return createLookupResultExpr( lookupResult, nullptr, expr->loc); } getSink()->diagnose(expr, Diagnostics::undefinedIdentifier2, expr->name); return expr; } RefPtr visitTypeCastExpr(TypeCastExpr * expr) { // Check the term we are applying first auto funcExpr = expr->FunctionExpr; funcExpr = CheckTerm(funcExpr); // Now ensure that the term represnets a (proper) type. TypeExp typeExp; typeExp.exp = funcExpr; typeExp = CheckProperType(typeExp); expr->FunctionExpr = typeExp.exp; expr->type.type = typeExp.type; // Next check the argument expression (there should be only one) for (auto & arg : expr->Arguments) { arg = CheckExpr(arg); } // Now process this like any other explicit call (so casts // and constructor calls are semantically equivalent). return CheckInvokeExprWithCheckedOperands(expr); #if 0 expr->Expression = CheckTerm(expr->Expression); auto targetType = CheckProperType(expr->TargetType); expr->TargetType = targetType; // The way to perform casting depends on the types involved if (expr->Expression->type->Equals(getSession()->getErrorType())) { // If the expression being casted has an error type, then just silently succeed expr->type = targetType.Ptr(); return expr; } else if (auto targetArithType = targetType->AsArithmeticType()) { if (auto exprArithType = expr->Expression->type->AsArithmeticType()) { // Both source and destination types are arithmetic, so we might // have a valid cast auto targetScalarType = targetArithType->GetScalarType(); auto exprScalarType = exprArithType->GetScalarType(); if (!IsNumeric(exprScalarType->baseType)) goto fail; if (!IsNumeric(targetScalarType->baseType)) goto fail; // TODO(tfoley): this checking is incomplete here, and could // lead to downstream compilation failures expr->type = targetType.Ptr(); return expr; } } // TODO: other cases? Should we allow a cast to succeeed whenever // a single-argument constructor for the target type would work? fail: // Default: in no other case succeds, then the cast failed and we emit a diagnostic. getSink()->diagnose(expr, Diagnostics::invalidTypeCast, expr->Expression->type, targetType->ToString()); expr->type = QualType(getSession()->getErrorType()); return expr; #endif } // Get the type to use when referencing a declaration QualType GetTypeForDeclRef(DeclRef declRef) { return getTypeForDeclRef( getSession(), this, getSink(), declRef, &typeResult); } // // Some syntax nodes should not occur in the concrete input syntax, // and will only appear *after* checking is complete. We need to // deal with this cases here, even if they are no-ops. // RefPtr visitDerefExpr(DerefExpr* expr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); return expr; } RefPtr visitSwizzleExpr(SwizzleExpr* expr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); return expr; } RefPtr visitOverloadedExpr(OverloadedExpr* expr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); return expr; } RefPtr visitOverloadedExpr2(OverloadedExpr2* expr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); return expr; } RefPtr visitAggTypeCtorExpr(AggTypeCtorExpr* expr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), expr, "should not appear in input syntax"); return expr; } // // // RefPtr MaybeDereference(RefPtr inExpr) { RefPtr expr = inExpr; for (;;) { auto& type = expr->type; if (auto pointerLikeType = type->As()) { type = QualType(pointerLikeType->elementType); auto derefExpr = new DerefExpr(); derefExpr->base = expr; derefExpr->type = QualType(pointerLikeType->elementType); // TODO(tfoley): deal with l-value-ness here expr = derefExpr; continue; } // Default case: just use the expression as-is return expr; } } RefPtr CheckSwizzleExpr( MemberExpr* memberRefExpr, RefPtr baseElementType, IntegerLiteralValue baseElementCount) { RefPtr swizExpr = new SwizzleExpr(); swizExpr->loc = memberRefExpr->loc; swizExpr->base = memberRefExpr->BaseExpression; IntegerLiteralValue limitElement = baseElementCount; int elementIndices[4]; int elementCount = 0; bool elementUsed[4] = { false, false, false, false }; bool anyDuplicates = false; bool anyError = false; auto swizzleText = getText(memberRefExpr->name); for (UInt i = 0; i < swizzleText.Length(); i++) { auto ch = swizzleText[i]; int elementIndex = -1; switch (ch) { case 'x': case 'r': elementIndex = 0; break; case 'y': case 'g': elementIndex = 1; break; case 'z': case 'b': elementIndex = 2; break; case 'w': case 'a': elementIndex = 3; break; default: // An invalid character in the swizzle is an error getSink()->diagnose(swizExpr, Diagnostics::invalidSwizzleExpr, swizzleText, baseElementType->ToString()); anyError = true; continue; } // TODO(tfoley): GLSL requires that all component names // come from the same "family"... // Make sure the index is in range for the source type if (elementIndex >= limitElement) { getSink()->diagnose(swizExpr, Diagnostics::invalidSwizzleExpr, swizzleText, baseElementType->ToString()); anyError = true; continue; } // Check if we've seen this index before for (int ee = 0; ee < elementCount; ee++) { if (elementIndices[ee] == elementIndex) anyDuplicates = true; } // add to our list... elementIndices[elementCount++] = elementIndex; } for (int ee = 0; ee < elementCount; ++ee) { swizExpr->elementIndices[ee] = elementIndices[ee]; } swizExpr->elementCount = elementCount; if (anyError) { return CreateErrorExpr(memberRefExpr); } else if (elementCount == 1) { // single-component swizzle produces a scalar // // Note(tfoley): the official HLSL rules seem to be that it produces // a one-component vector, which is then implicitly convertible to // a scalar, but that seems like it just adds complexity. swizExpr->type = QualType(baseElementType); } else { // TODO(tfoley): would be nice to "re-sugar" type // here if the input type had a sugared name... swizExpr->type = QualType(createVectorType( baseElementType, new ConstantIntVal(elementCount))); } // A swizzle can be used as an l-value as long as there // were no duplicates in the list of components swizExpr->type.IsLeftValue = !anyDuplicates; return swizExpr; } RefPtr CheckSwizzleExpr( MemberExpr* memberRefExpr, RefPtr baseElementType, RefPtr baseElementCount) { if (auto constantElementCount = baseElementCount.As()) { return CheckSwizzleExpr(memberRefExpr, baseElementType, constantElementCount->value); } else { getSink()->diagnose(memberRefExpr, Diagnostics::unimplemented, "swizzle on vector of unknown size"); return CreateErrorExpr(memberRefExpr); } } RefPtr visitStaticMemberExpr(StaticMemberExpr* /*expr*/) { SLANG_UNEXPECTED("should not occur in unchecked AST"); UNREACHABLE_RETURN(nullptr); } RefPtr lookupResultFailure( MemberExpr* expr, QualType const& baseType) { getSink()->diagnose(expr, Diagnostics::noMemberOfNameInType, expr->name, baseType); expr->type = QualType(getSession()->getErrorType()); return expr; } RefPtr visitMemberExpr(MemberExpr * expr) { expr->BaseExpression = CheckExpr(expr->BaseExpression); expr->BaseExpression = MaybeDereference(expr->BaseExpression); auto & baseType = expr->BaseExpression->type; // Note: Checking for vector types before declaration-reference types, // because vectors are also declaration reference types... // // Also note: the way this is done right now means that the ability // to swizzle vectors interferes with any chance of looking up // members via extension, for vector or scalar types. // // TODO: Matrix swizzles probably need to be handled at some point. if (auto baseVecType = baseType->AsVectorType()) { return CheckSwizzleExpr( expr, baseVecType->elementType, baseVecType->elementCount); } else if(auto baseScalarType = baseType->AsBasicType()) { // Treat scalar like a 1-element vector when swizzling return CheckSwizzleExpr( expr, baseScalarType, 1); } else if(auto typeType = baseType->As()) { // We are looking up a member inside a type. // We want to be careful here because we should only find members // that are implicitly or explicitly `static`. // // TODO: this duplicates a *lot* of logic with the case below. // We need to fix that. auto type = typeType->type; if (type->As()) { return CreateErrorExpr(expr); } LookupResult lookupResult = lookUpMember( getSession(), this, expr->name, type); if (!lookupResult.isValid()) { return lookupResultFailure(expr, baseType); } // We need to confirm that whatever member we // are trying to refer to is usable via static reference. // // TODO: eventually we might allow a non-static // member to be adapted by turning it into something // like a closure that takes the missing `this` parameter. // // E.g., a static reference to a method could be treated // as a value with a function type, where the first parameter // is `type`. // // The biggest challenge there is that we'd need to arrange // to generate "dispatcher" functions that could be used // to implement that function, in the case where we are // making a static reference to some kind of polymoprhic declaration. // // (Also, static refernces to fields/properties would get even // harder, because you'd have to know whether a getter/setter/ref-er // is needed). // // For now let's just be expedient and disallow all of that, because // we can always add it back in later. if(!lookupResult.isOverloaded()) { // The non-overloaded case is relatively easy. We just want // to look at the member being referenced, and check if // it is allowed in a `static` context: // if(!isUsableAsStaticMember(lookupResult.item)) { getSink()->diagnose( expr->loc, Diagnostics::staticRefToNonStaticMember, type, expr->name); } } else { // The overloaded case is trickier, because we should first // filter the list of candidates, because if there is anything // that *is* usable in a static context, then we should assume // the user just wants to reference that. We should only // issue an error if *all* of the items that were discovered // are non-static. bool anyNonStatic = false; List staticItems; for(auto item : lookupResult.items) { // Is this item usable as a static member? if(isUsableAsStaticMember(item)) { // If yes, then it will be part of the output. staticItems.Add(item); } else { // If no, then we might need to output an error. anyNonStatic = true; } } // Was there anything non-static in the list? if(anyNonStatic) { // If we had some static items, then that's okay, // we just want to use our newly-filtered list. if(staticItems.Count()) { lookupResult.items = staticItems; } else { // Otherwise, it is time to report an error. getSink()->diagnose( expr->loc, Diagnostics::staticRefToNonStaticMember, type, expr->name); } } // If there were no non-static items, then the `items` // array already represents what we'd get by filtering... } return createLookupResultExpr( lookupResult, expr->BaseExpression, expr->loc); } else if (baseType->As()) { return CreateErrorExpr(expr); } else { LookupResult lookupResult = lookUpMember( getSession(), this, expr->name, baseType.Ptr()); if (!lookupResult.isValid()) { return lookupResultFailure(expr, baseType); } // TODO: need to filter for declarations that are valid to refer // to in this context... return createLookupResultExpr( lookupResult, expr->BaseExpression, expr->loc); } } SemanticsVisitor & operator = (const SemanticsVisitor &) = delete; // RefPtr visitInitializerListExpr(InitializerListExpr* expr) { // When faced with an initializer list, we first just check the sub-expressions blindly. // Actually making them conform to a desired type will wait for when we know the desired // type based on context. for( auto& arg : expr->args ) { arg = CheckTerm(arg); } expr->type = getSession()->getInitializerListType(); return expr; } void importModuleIntoScope(Scope* scope, ModuleDecl* moduleDecl) { // If we've imported this one already, then // skip the step where we modify the current scope. if (importedModules.Contains(moduleDecl)) { return; } importedModules.Add(moduleDecl); // Create a new sub-scope to wire the module // into our lookup chain. auto subScope = new Scope(); subScope->containerDecl = moduleDecl; subScope->nextSibling = scope->nextSibling; scope->nextSibling = subScope; // Also import any modules from nested `import` declarations // with the `__exported` modifier for (auto importDecl : moduleDecl->getMembersOfType()) { if (!importDecl->HasModifier()) continue; importModuleIntoScope(scope, importDecl->importedModuleDecl.Ptr()); } } void visitEmptyDecl(EmptyDecl* /*decl*/) { // nothing to do } void visitImportDecl(ImportDecl* decl) { if(decl->IsChecked(DeclCheckState::CheckedHeader)) return; // We need to look for a module with the specified name // (whether it has already been loaded, or needs to // be loaded), and then put its declarations into // the current scope. auto name = decl->moduleNameAndLoc.name; auto scope = decl->scope; // Try to load a module matching the name auto importedModuleDecl = findOrImportModule(request, name, decl->moduleNameAndLoc.loc); // If we didn't find a matching module, then bail out if (!importedModuleDecl) return; // Record the module that was imported, so that we can use // it later during code generation. decl->importedModuleDecl = importedModuleDecl; importModuleIntoScope(scope.Ptr(), importedModuleDecl.Ptr()); decl->SetCheckState(getCheckedState()); } // Perform semantic checking of an object-oriented `this` // expression. RefPtr visitThisExpr(ThisExpr* expr) { // We will do an upwards search starting in the current // scope, looking for a surrounding type (or `extension`) // declaration that could be the referrant of the expression. auto scope = expr->scope; while (scope) { auto containerDecl = scope->containerDecl; if (auto aggTypeDecl = containerDecl->As()) { checkDecl(aggTypeDecl); // Okay, we are using `this` in the context of an // aggregate type, so the expression should be // of the corresponding type. expr->type = DeclRefType::Create( getSession(), makeDeclRef(aggTypeDecl)); return expr; } else if (auto extensionDecl = containerDecl->As()) { checkDecl(extensionDecl); // When `this` is used in the context of an `extension` // declaration, then it should refer to an instance of // the type being extended. // // TODO: There is potentially a small gotcha here that // lookup through such a `this` expression should probably // prioritize members declared in the current extension // if there are multiple extensions in scope that add // members with the same name... // expr->type = QualType(extensionDecl->targetType.type); return expr; } scope = scope->parent; } getSink()->diagnose(expr, Diagnostics::thisExpressionOutsideOfTypeDecl); return CreateErrorExpr(expr); } }; bool isPrimaryDecl( CallableDecl* decl) { SLANG_ASSERT(decl); return (!decl->primaryDecl) || (decl == decl->primaryDecl); } RefPtr checkProperType(TranslationUnitRequest * tu, TypeExp typeExp) { RefPtr type; DiagnosticSink nSink; nSink.sourceManager = tu->compileRequest->sourceManager; SemanticsVisitor visitor( &nSink, tu->compileRequest, tu); auto typeOut = visitor.CheckProperType(typeExp); if (!nSink.errorCount) { type = typeOut.type; } return type; } // Validate that an entry point function conforms to any additional // constraints based on the stage (and profile?) it specifies. void validateEntryPoint( EntryPointRequest* /*entryPoint*/) { // TODO: We currently don't do any checking here, but this is the // right place to perform the following validation checks: // // * Does the entry point specify all of the attributes required // by the chosen stage (e.g., a `[domain(...)]` attribute for] // a hull shader. // // * Are the function input/output parameters and result type // all valid for the chosen stage? (e.g., there shouldn't be // an `OutputStream` type in a vertex shader signature) // // * For any varying input/output, are there semantics specified // (Note: this potentially overlaps with layout logic...), and // are the system-value semantics valid for the given stage? // // There's actually a lot of detail to semantic checking, in // that the AST-level code should probably be validating the // use of system-value semantics by linking them to explicit // declarations in the standard library. We should also be // using profile information on those declarations to infer // appropriate profile restrictions on the entry point. // // * Is the entry point actually usable on the given stage/profile? // E.g., if we have a vertex shader that (transitively) calls // `Texture2D.Sample`, then that should produce an error because // that function is specific to the fragment profile/stage. // } // Given an `EntryPointRequest` specified via API or command line options, // attempt to find a matching AST declaration that implements the specified // entry point. If such a function is found, then validate that it actually // meets the requirements for the selected stage/profile. // void findAndValidateEntryPoint( EntryPointRequest* entryPoint) { // The first step in validating the entry point is to find // the (unique) function declaration that matches its name. auto translationUnit = entryPoint->getTranslationUnit(); auto sink = &entryPoint->compileRequest->mSink; auto translationUnitSyntax = translationUnit->SyntaxNode; // Make sure we've got a query-able member dictionary buildMemberDictionary(translationUnitSyntax); // We will look up any global-scope declarations in the translation // unit that match the name of our entry point. Decl* firstDeclWithName = nullptr; if( !translationUnitSyntax->memberDictionary.TryGetValue(entryPoint->name, firstDeclWithName) ) { // If there doesn't appear to be any such declaration, then // we need to diagnose it as an error, and then bail out. sink->diagnose(translationUnitSyntax, Diagnostics::entryPointFunctionNotFound, entryPoint->name); return; } // We found at least one global-scope declaration with the right name, // but (1) it might not be a function, and (2) there might be // more than one function. // // We'll walk the linked list of declarations with the same name, // to see what we find. Along the way we'll keep track of the // first function declaration we find, if any: FuncDecl* entryPointFuncDecl = nullptr; for(auto ee = firstDeclWithName; ee; ee = ee->nextInContainerWithSameName) { // Is this declaration a function? if (auto funcDecl = dynamic_cast(ee)) { // Skip non-primary declarations, so that // we don't give an error when an entry // point is forward-declared. if (!isPrimaryDecl(funcDecl)) continue; // is this the first one we've seen? if (!entryPointFuncDecl) { // If so, this is a candidate to be // the entry point function. entryPointFuncDecl = funcDecl; } else { // Uh-oh! We've already seen a function declaration with this // name before, so the whole thing is ambiguous. We need // to diagnose and bail out. sink->diagnose(translationUnitSyntax, Diagnostics::ambiguousEntryPoint, entryPoint->name); // List all of the declarations that the user *might* mean for (auto ff = firstDeclWithName; ff; ff = ff->nextInContainerWithSameName) { if (auto candidate = dynamic_cast(ff)) { sink->diagnose(candidate, Diagnostics::entryPointCandidate, candidate->getName()); } } // Bail out. return; } } } // Did we find a function declaration in our search? if(!entryPointFuncDecl) { // If not, then we need to diagnose the error. // For convenience, we will point to the first // declaration with the right name, that wasn't a function. sink->diagnose(firstDeclWithName, Diagnostics::entryPointSymbolNotAFunction, entryPoint->name); return; } // TODO: it is possible that the entry point was declared with // profile or target overloading. Is there anything that we need // to do at this point to filter out declarations that aren't // relevant to the selected profile for the entry point? // Phew, we have at least found a suitable decl. // Let's record that in the entry-point request so // that we don't have to re-do this effort again later. entryPoint->decl = entryPointFuncDecl; // Lookup generic parameter types in global scope List> scopesToTry; scopesToTry.Add(entryPoint->getTranslationUnit()->SyntaxNode->scope); for (auto & module : entryPoint->compileRequest->loadedModulesList) scopesToTry.Add(module->moduleDecl->scope); List> globalGenericArgs; for (auto name : entryPoint->genericParameterTypeNames) { // parse type name RefPtr type; for (auto & s : scopesToTry) { RefPtr typeExpr = entryPoint->compileRequest->parseTypeString(entryPoint->getTranslationUnit(), name, s); type = checkProperType(translationUnit, TypeExp(typeExpr)); if (type) { break; } } if (!type) { sink->diagnose(firstDeclWithName, Diagnostics::entryPointTypeSymbolNotAType, name); return; } globalGenericArgs.Add(type); } // validate global type arguments only when we are generating code if ((entryPoint->compileRequest->compileFlags & SLANG_COMPILE_FLAG_NO_CODEGEN) == 0) { // check that user-provioded type arguments conforms to the generic type // parameter declaration of this translation unit // collect global generic parameters from all imported modules List> globalGenericParams; // add current translation unit first { auto globalGenParams = translationUnit->SyntaxNode->getMembersOfType(); for (auto p : globalGenParams) globalGenericParams.Add(p); } // add imported modules for (auto loadedModule : entryPoint->compileRequest->loadedModulesList) { auto moduleDecl = loadedModule->moduleDecl; auto globalGenParams = moduleDecl->getMembersOfType(); for (auto p : globalGenParams) globalGenericParams.Add(p); } if (globalGenericParams.Count() != globalGenericArgs.Count()) { sink->diagnose(entryPoint->decl, Diagnostics::mismatchEntryPointTypeArgument, globalGenericParams.Count(), globalGenericArgs.Count()); return; } // We have an appropriate number of arguments for the global generic parameters, // and now we need to check that the arguments conform to the declared constraints. // // Along the way, we will build up an appropriate set of substitutions to represent // the generic arguments and their conformances. // RefPtr globalGenericSubsts; auto globalGenericSubstLink = &globalGenericSubsts; // // TODO: There is a serious flaw to this checking logic if we ever have cases where // the constraints on one `type_param` can depend on another `type_param`, e.g.: // // type_param A; // type_param B : ISidekick; // // In that case, if a user tries to set `B` to `Robin` and `Robin` conforms to // `ISidekick`, then the compiler needs to know whether `A` is being // set to `Batman` to know whether the setting for `B` is valid. In this limit // the constraints can be mutually recursive (so `A : IMentor`). // // The only way to check things corectly is to validate each conformance under // a set of assumptions (substitutions) that includes all the type substitutions, // and possibly also all the other constraints *except* the one to be validated. // // We will punt on this for now, and just check each constraint in isolation. // UInt argCounter = 0; for(auto& globalGenericParam : globalGenericParams) { // Get the argument that matches this parameter. UInt argIndex = argCounter++; SLANG_ASSERT(argIndex < globalGenericArgs.Count()); auto globalGenericArg = globalGenericArgs[argIndex]; // Create a substitution for this parameter/argument. RefPtr subst = new GlobalGenericParamSubstitution(); subst->paramDecl = globalGenericParam; subst->actualType = globalGenericArg; // Walk through the declared constraints for the parameter, // and check that the argument actually satisfies them. for(auto constraint : globalGenericParam->getMembersOfType()) { // Get the type that the constraint is enforcing conformance to auto interfaceType = GetSup(DeclRef(constraint, nullptr)); // Use our semantic-checking logic to search for a witness to the required conformance SemanticsVisitor visitor(sink, entryPoint->compileRequest, translationUnit); auto witness = visitor.tryGetSubtypeWitness(globalGenericArg, interfaceType); if (!witness) { // If no witness was found, then we will be unable to satisfy // the conformances required. sink->diagnose(globalGenericParam, Diagnostics::typeArgumentDoesNotConformToInterface, globalGenericParam->nameAndLoc.name, globalGenericArg, interfaceType); } // Attach the concrete witness for this conformance to the // substutiton GlobalGenericParamSubstitution::ConstraintArg constraintArg; constraintArg.decl = constraint; constraintArg.val = witness; subst->constraintArgs.Add(constraintArg); } // Add the substitution for this parameter to the global substitution // set that we are building. *globalGenericSubstLink = subst; globalGenericSubstLink = &subst->outer; } entryPoint->globalGenericSubst = globalGenericSubsts; } if (sink->errorCount != 0) return; // Now that we've *found* the entry point, it is time to validate // that it actually meets the constraints for the chosen stage/profile. // // TODO: This validation should be performed "under" any global generic // parameter substitution we might have created, so that we can validate // based on knowledge of actual types. // validateEntryPoint(entryPoint); } void validateEntryPoints( CompileRequest* compileRequest) { // The validation of entry points here will be modal, and controlled // by whether the user specified any entry points directly via // API or command-line options. // // TODO: We may want to make this choice explicit rather than implicit. // // First, check if the user request any entry points explicitly via // the API or command line. // bool anyExplicitEntryPointRequests = false; for (auto& translationUnit : compileRequest->translationUnits) { if( translationUnit->entryPoints.Count() != 0) { anyExplicitEntryPointRequests = true; break; } } if( anyExplicitEntryPointRequests ) { // If there were any explicit requests for entry points to be // checked, then we will *only* check those. for (auto& translationUnit : compileRequest->translationUnits) { for (auto entryPoint : translationUnit->entryPoints) { findAndValidateEntryPoint(entryPoint); } } } else { // Otherwise, scan for any `[shader(...)]` attributes in // the user's code, and construct `EntryPointRequest`s to // represent them. // // This ensures that downstream code only has to consider // the central list of entry point requests, and doesn't // have to know where they came from. // TODO: A comprehensive approach here would need to search // recursively for entry points, because they might appear // as, e.g., member function of a `struct` type. // // For now we'll start with an extremely basic approach that // should work for typical HLSL code. // UInt translationUnitCount = compileRequest->translationUnits.Count(); for(UInt tt = 0; tt < translationUnitCount; ++tt) { auto translationUnit = compileRequest->translationUnits[tt]; for( auto globalDecl : translationUnit->SyntaxNode->Members ) { auto maybeFuncDecl = globalDecl; if( auto genericDecl = maybeFuncDecl->As() ) { maybeFuncDecl = genericDecl->inner; } auto funcDecl = maybeFuncDecl->As(); if(!funcDecl) continue; auto entryPointAttr = funcDecl->FindModifier(); if(!entryPointAttr) continue; // We've discovered a valid entry point. It is a function (possibly // generic) that has a `[shader(...)]` attribute to mark it as an // entry point. // // We will now register that entry point as an `EntryPointRequest` // with an appropriately chosen profile. // // The profile will only include a stage, so that the profile "family" // and "version" are left unspecified. Downstream code will need // to be able to handle this case. // Profile profile; profile.setStage(entryPointAttr->stage); // We manually fill in the entry point request object. RefPtr entryPointReq = new EntryPointRequest(); entryPointReq->compileRequest = compileRequest; entryPointReq->translationUnitIndex = int(tt); entryPointReq->decl = funcDecl; entryPointReq->name = funcDecl->getName(); entryPointReq->profile = profile; // Apply the common validation logic to this entry point. validateEntryPoint(entryPointReq); // Add the entry point to the list in the translation unit, // and also the global list in the compile request. translationUnit->entryPoints.Add(entryPointReq); compileRequest->entryPoints.Add(entryPointReq); } } } } void checkTranslationUnit( TranslationUnitRequest* translationUnit) { SemanticsVisitor visitor( &translationUnit->compileRequest->mSink, translationUnit->compileRequest, translationUnit); // Apply the visitor to do the main semantic // checking that is required on all declarations // in the translation unit. visitor.checkDecl(translationUnit->SyntaxNode); } // // Get the type to use when referencing a declaration QualType getTypeForDeclRef( Session* session, SemanticsVisitor* sema, DiagnosticSink* sink, DeclRef declRef, RefPtr* outTypeResult) { if( sema ) { sema->checkDecl(declRef.getDecl()); } // We need to insert an appropriate type for the expression, based on // what we found. if (auto varDeclRef = declRef.As()) { QualType qualType; qualType.type = GetType(varDeclRef); bool isLValue = true; if(varDeclRef.getDecl()->FindModifier()) isLValue = false; qualType.IsLeftValue = isLValue; return qualType; } else if (auto typeAliasDeclRef = declRef.As()) { auto type = getNamedType(session, typeAliasDeclRef); *outTypeResult = type; return QualType(getTypeType(type)); } else if (auto aggTypeDeclRef = declRef.As()) { auto type = DeclRefType::Create(session, aggTypeDeclRef); *outTypeResult = type; return QualType(getTypeType(type)); } else if (auto simpleTypeDeclRef = declRef.As()) { auto type = DeclRefType::Create(session, simpleTypeDeclRef); *outTypeResult = type; return QualType(getTypeType(type)); } else if (auto genericDeclRef = declRef.As()) { auto type = getGenericDeclRefType(session, genericDeclRef); *outTypeResult = type; return QualType(getTypeType(type)); } else if (auto funcDeclRef = declRef.As()) { auto type = getFuncType(session, funcDeclRef); return QualType(type); } else if (auto constraintDeclRef = declRef.As()) { // When we access a constraint or an inheritance decl (as a member), // we are conceptually performing a "cast" to the given super-type, // with the declaration showing that such a cast is legal. auto type = GetSup(constraintDeclRef); return QualType(type); } if( sink ) { sink->diagnose(declRef, Diagnostics::unimplemented, "cannot form reference to this kind of declaration"); } return QualType(session->getErrorType()); } QualType getTypeForDeclRef( Session* session, DeclRef declRef) { RefPtr typeResult; return getTypeForDeclRef(session, nullptr, nullptr, declRef, &typeResult); } DeclRef ApplyExtensionToType( SemanticsVisitor* semantics, ExtensionDecl* extDecl, RefPtr type) { if(!semantics) return DeclRef(); return semantics->ApplyExtensionToType(extDecl, type); } RefPtr createDefaultSubsitutionsForGeneric( Session* session, GenericDecl* genericDecl, RefPtr outerSubst) { RefPtr genericSubst = new GenericSubstitution(); genericSubst->genericDecl = genericDecl; genericSubst->outer = outerSubst; for( auto mm : genericDecl->Members ) { if( auto genericTypeParamDecl = mm.As() ) { genericSubst->args.Add(DeclRefType::Create(session, DeclRef(genericTypeParamDecl.Ptr(), outerSubst))); } else if( auto genericValueParamDecl = mm.As() ) { genericSubst->args.Add(new GenericParamIntVal(DeclRef(genericValueParamDecl.Ptr(), outerSubst))); } } // create default substitution arguments for constraints for (auto mm : genericDecl->Members) { if (auto genericTypeConstraintDecl = mm.As()) { RefPtr witness = new DeclaredSubtypeWitness(); witness->declRef = DeclRef(genericTypeConstraintDecl.Ptr(), outerSubst); witness->sub = genericTypeConstraintDecl->sub.type; witness->sup = genericTypeConstraintDecl->sup.type; genericSubst->args.Add(witness); } } return genericSubst; } // Sometimes we need to refer to a declaration the way that it would be specialized // inside the context where it is declared (e.g., with generic parameters filled in // using their archetypes). // SubstitutionSet createDefaultSubstitutions( Session* session, Decl* decl, SubstitutionSet outerSubstSet) { auto dd = decl->ParentDecl; if( auto genericDecl = dynamic_cast(dd) ) { // We don't want to specialize references to anything // other than the "inner" declaration itself. if(decl != genericDecl->inner) return outerSubstSet; RefPtr genericSubst = createDefaultSubsitutionsForGeneric( session, genericDecl, outerSubstSet.substitutions); return SubstitutionSet(genericSubst); } return outerSubstSet; } SubstitutionSet createDefaultSubstitutions( Session* session, Decl* decl) { SubstitutionSet subst; if( auto parentDecl = decl->ParentDecl ) { subst = createDefaultSubstitutions(session, parentDecl); } subst = createDefaultSubstitutions(session, decl, subst); return subst; } void checkDecl(SemanticsVisitor* visitor, Decl* decl) { visitor->checkDecl(decl); } }