// slang-check-decl.cpp #include "slang-check-impl.h" // This file constaints the semantic checking logic and // related queries for declarations. // // Because declarations are the top-level construct // of the AST (in turn containing all the statements, // types, and expressions), the declaration-checking // logic also orchestrates the overall flow and how // and when things get checked. #include "slang-lookup.h" namespace Slang { /// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration? bool isEffectivelyStatic( Decl* decl, ContainerDecl* parentDecl) { // Things at the global scope are always "members" of their module. // if(as(parentDecl)) return false; // Anything explicitly marked `static` and not at module scope // counts as a static rather than instance declaration. // if(decl->HasModifier()) return true; // Next we need to deal with cases where a declaration is // effectively `static` even if the language doesn't make // the user say so. Most languages make the default assumption // that nested types are `static` even if they don't say // so (Java is an exception here, perhaps due to some // influence from the Scandanavian OOP tradition of Beta/gbeta). // if(as(decl)) return true; if(as(decl)) return true; // Things nested inside functions may have dependencies // on values from the enclosing scope, but this needs to // be dealt with via "capture" so they are also effectively // `static` // if(as(parentDecl)) return true; // Type constraint declarations are used in member-reference // context as a form of casting operation, so we treat them // as if they are instance members. This is a bit of a hack, // but it achieves the result we want until we have an // explicit representation of up-cast operations in the // AST. // if(as(decl)) return false; return false; } bool isEffectivelyStatic( Decl* decl) { // For the purposes of an ordinary declaration, when determining if // it is static or per-instance, the "parent" declaration we really // care about is the next outer non-generic declaration. // // TODO: This idiom of getting the "next outer non-generic declaration" // comes up just enough that we should probably have a convenience // function for it. auto parentDecl = decl->ParentDecl; if(auto genericDecl = as(parentDecl)) parentDecl = genericDecl->ParentDecl; return isEffectivelyStatic(decl, parentDecl); } /// Is `decl` a global shader parameter declaration? bool isGlobalShaderParameter(VarDeclBase* decl) { // A global shader parameter must be declared at global (module) scope. // if(!as(decl->ParentDecl)) return false; // A global variable marked `static` indicates a traditional // global variable (albeit one that is implicitly local to // the translation unit) // if(decl->HasModifier()) return false; // The `groupshared` modifier indicates that a variable cannot // be a shader parameters, but is instead transient storage // allocated for the duration of a thread-group's execution. // if(decl->HasModifier()) return false; return true; } // 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; // Global-scope shader parameters should not be writable, // since they are effectively program inputs. // // TODO: We could eventually treat a mutable global shader // parameter as a shorthand for an immutable parameter and // a global variable that gets initialized from that parameter, // but in order to do so we'd need to support global variables // with resource types better in the back-end. // if(isGlobalShaderParameter(varDeclRef.getDecl())) isLValue = false; // Variables declared with `let` are always immutable. if(varDeclRef.is()) isLValue = false; // Generic value parameters are always immutable if(varDeclRef.is()) isLValue = false; // Function parameters declared in the "modern" style // are immutable unless they have an `out` or `inout` modifier. if(varDeclRef.is()) { // Note: the `inout` modifier AST class inherits from // the class for the `out` modifier so that we can // make simple checks like this. // if( !varDeclRef.getDecl()->HasModifier() ) { isLValue = false; } } qualType.IsLeftValue = isLValue; return qualType; } else if( auto enumCaseDeclRef = declRef.as() ) { QualType qualType; qualType.type = getType(enumCaseDeclRef); qualType.IsLeftValue = false; 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 = as(mm) ) { genericSubst->args.add(DeclRefType::Create(session, DeclRef(genericTypeParamDecl, outerSubst))); } else if( auto genericValueParamDecl = as(mm) ) { genericSubst->args.add(new GenericParamIntVal(DeclRef(genericValueParamDecl, outerSubst))); } } // create default substitution arguments for constraints for (auto mm : genericDecl->Members) { if (auto genericTypeConstraintDecl = as(mm)) { RefPtr witness = new DeclaredSubtypeWitness(); witness->declRef = DeclRef(genericTypeConstraintDecl, 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 = as(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); } bool SemanticsVisitor::isDeclUsableAsStaticMember( Decl* decl) { if(decl->HasModifier()) return true; if(as(decl)) return true; if(as(decl)) return true; if(as(decl)) return true; if(as(decl)) return true; return false; } bool SemanticsVisitor::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); } // 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 SemanticsVisitor::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. // getSink()->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 = as(decl)) { if (auto parenScope = as(varDecl->ParentDecl)) { // 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. getSink()->diagnose(decl, Diagnostics::localVariableUsedBeforeDeclared, decl); return; } } if (DeclCheckState::CheckingHeader > decl->checkState) { decl->SetCheckState(DeclCheckState::CheckingHeader); } // Check the modifiers on the declaration first, in case // semantics of the body itself will depend on them. checkModifiers(decl); // Use visitor pattern to dispatch to correct case dispatchDecl(decl); if(state > decl->checkState) { decl->SetCheckState(state); } } void SemanticsVisitor::EnusreAllDeclsRec(RefPtr decl) { checkDecl(decl); if (auto containerDecl = as(decl)) { for (auto m : containerDecl->Members) { EnusreAllDeclsRec(m); } } } void SemanticsVisitor::CheckVarDeclCommon(RefPtr varDecl) { // A variable that didn't have an explicit type written must // have its type inferred from the initial-value expression. // if(!varDecl->type.exp) { // In this case we need to perform all checking of the // variable (including semantic checking of the initial-value // expression) during the first phase of checking. auto initExpr = varDecl->initExpr; if(!initExpr) { getSink()->diagnose(varDecl, Diagnostics::varWithoutTypeMustHaveInitializer); varDecl->type.type = getSession()->getErrorType(); } else { initExpr = CheckExpr(initExpr); // TODO: We might need some additional steps here to ensure // that the type of the expression is one we are okay with // inferring. E.g., if we ever decide that integer and floating-point // literals have a distinct type from the standard int/float types, // then we would need to "decay" a literal to an explicit type here. varDecl->initExpr = initExpr; varDecl->type.type = initExpr->type; } varDecl->SetCheckState(DeclCheckState::Checked); } else { if (function || checkingPhase == CheckingPhase::Header) { TypeExp typeExp = CheckUsableType(varDecl->type); 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); initExpr = coerce(varDecl->type.Ptr(), 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); } } } varDecl->SetCheckState(getCheckedState()); } // Fill in default substitutions for the 'subtype' part of a type constraint decl void SemanticsVisitor::CheckConstraintSubType(TypeExp& typeExp) { if (auto sharedTypeExpr = as(typeExp.exp)) { if (auto declRefType = as(sharedTypeExpr->base)) { declRefType->declRef.substitutions = createDefaultSubstitutions(getSession(), declRefType->declRef.getDecl()); if (auto typetype = as(typeExp.exp->type)) typetype->type = declRefType; } } } void SemanticsVisitor::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 SemanticsVisitor::checkDecl(Decl* decl) { EnsureDecl(decl, checkingPhase == CheckingPhase::Header ? DeclCheckState::CheckedHeader : DeclCheckState::Checked); } void SemanticsVisitor::checkGenericDeclHeader(GenericDecl* genericDecl) { if (genericDecl->IsChecked(DeclCheckState::CheckedHeader)) return; // check the parameters for (auto m : genericDecl->Members) { if (auto typeParam = as(m)) { typeParam->initType = CheckProperType(typeParam->initType); } else if (auto valParam = as(m)) { // TODO: some real checking here... CheckVarDeclCommon(valParam); } else if (auto constraint = as(m)) { CheckGenericConstraintDecl(constraint); } } genericDecl->SetCheckState(DeclCheckState::CheckedHeader); } void SemanticsVisitor::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 SemanticsVisitor::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 SemanticsVisitor::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 = as(base.type)) { if(auto interfaceDeclRef = declRefType->declRef.as()) { return; } } else if(base.type.is()) { // If an error was already produced, don't emit a cascading error. 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); } void SemanticsVisitor::visitSyntaxDecl(SyntaxDecl*) { // These are only used in the stdlib, so no checking is needed } void SemanticsVisitor::visitAttributeDecl(AttributeDecl*) { // These are only used in the stdlib, so no checking is needed } void SemanticsVisitor::visitGenericTypeParamDecl(GenericTypeParamDecl*) { // These are only used in the stdlib, so no checking is needed for now } void SemanticsVisitor::visitGenericValueParamDecl(GenericValueParamDecl*) { // These are only used in the stdlib, so no checking is needed for now } void SemanticsVisitor::checkInterfaceConformancesRec(Decl* decl) { // Any user-defined type may have declared interface conformances, // which we should check. // if( auto aggTypeDecl = as(decl) ) { checkAggTypeConformance(aggTypeDecl); } // Conformances can also come via `extension` declarations, and // we should check them against the type(s) being extended. // else if(auto extensionDecl = as(decl)) { checkExtensionConformance(extensionDecl); } // We need to handle the recursive cases here, the first // of which is a generic decl, where we want to recurivsely // check the inner declaration. // if(auto genericDecl = as(decl)) { checkInterfaceConformancesRec(genericDecl->inner); } // For any other kind of container declaration, we will // recurse into all of its member declarations, so that // we can handle, e.g., nested `struct` types. // else if(auto containerDecl = as(decl)) { for(auto member : containerDecl->Members) { checkInterfaceConformancesRec(member); } } } void SemanticsVisitor::visitModuleDecl(ModuleDecl* programNode) { // Try to register all the builtin decls for (auto decl : programNode->Members) { auto inner = decl; if (auto genericDecl = as(decl)) { 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 = as(g->inner)) { checkGenericDeclHeader(g); registerExtension(extDecl); } } // check user defined attribute classes first for (auto decl : programNode->Members) { if (auto typeMember = as(decl)) { bool isTypeAttributeClass = false; for (auto attrib : typeMember->GetModifiersOfType()) { if (attrib->name == getSession()->getNameObj("AttributeUsageAttribute")) { isTypeAttributeClass = true; break; } } if (isTypeAttributeClass) checkDecl(decl); } } // 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 (getSink()->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); } if (pass == 0) { checkInterfaceConformancesRec(programNode); } } } bool SemanticsVisitor::doesSignatureMatchRequirement( DeclRef satisfyingMemberDeclRef, DeclRef requiredMemberDeclRef, RefPtr witnessTable) { if(satisfyingMemberDeclRef.getDecl()->HasModifier() && !requiredMemberDeclRef.getDecl()->HasModifier()) { // A `[mutating]` method can't satisfy a non-`[mutating]` requirement, // but vice-versa is okay. return false; } if(satisfyingMemberDeclRef.getDecl()->HasModifier() != requiredMemberDeclRef.getDecl()->HasModifier()) { // A `static` method can't satisfy a non-`static` requirement and vice versa. return false; } // 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 SemanticsVisitor::doesGenericSignatureMatchRequirement( DeclRef genDecl, DeclRef requirementGenDecl, RefPtr witnessTable) { if (genDecl.getDecl()->Members.getCount() != requirementGenDecl.getDecl()->Members.getCount()) return false; for (Index i = 0; i < genDecl.getDecl()->Members.getCount(); i++) { auto genMbr = genDecl.getDecl()->Members[i]; auto requiredGenMbr = genDecl.getDecl()->Members[i]; if (auto genTypeMbr = as(genMbr)) { if (auto requiredGenTypeMbr = as(requiredGenMbr)) { } else return false; } else if (auto genValMbr = as(genMbr)) { if (auto requiredGenValMbr = as(requiredGenMbr)) { if (!genValMbr->type->Equals(requiredGenValMbr->type)) return false; } else return false; } else if (auto genTypeConstraintMbr = as(genMbr)) { if (auto requiredTypeConstraintMbr = as(requiredGenMbr)) { 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 substitutions 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 SemanticsVisitor::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 satisfied, then the chosen // type can indeed satisfy the interface requirement. witnessTable->requirementDictionary.Add( requiredAssociatedTypeDeclRef.getDecl(), RequirementWitness(satisfyingType)); } return conformance; } bool SemanticsVisitor::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; } bool SemanticsVisitor::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; } RefPtr SemanticsVisitor::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 SemanticsVisitor::checkConformanceToType( ConformanceCheckingContext* context, DeclRef typeDeclRef, InheritanceDecl* inheritanceDecl, Type* baseType) { if (auto baseDeclRefType = as(baseType)) { 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; } bool SemanticsVisitor::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 SemanticsVisitor::checkExtensionConformance(ExtensionDecl* decl) { if (auto targetDeclRefType = as(decl->targetType)) { if (auto aggTypeDeclRef = targetDeclRefType->declRef.as()) { for (auto inheritanceDecl : decl->getMembersOfType()) { checkConformance(aggTypeDeclRef, inheritanceDecl); } } } } void SemanticsVisitor::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 = as(decl)) { // Don't check that an interface conforms to the // things it inherits from. } else if (auto assocTypeDecl = as(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 SemanticsVisitor::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 implicit 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()); } bool SemanticsVisitor::isIntegerBaseType(BaseType baseType) { switch(baseType) { default: return false; case BaseType::Int8: case BaseType::Int16: case BaseType::Int: case BaseType::Int64: case BaseType::UInt8: case BaseType::UInt16: case BaseType::UInt: case BaseType::UInt64: return true; } } void SemanticsVisitor::validateEnumTagType(Type* type, SourceLoc const& loc) { if(auto basicType = as(type)) { // Allow the built-in integer types. if(isIntegerBaseType(basicType->baseType)) return; // By default, don't allow other types to be used // as an `enum` tag type. } getSink()->diagnose(loc, Diagnostics::invalidEnumTagType, type); } void SemanticsVisitor::visitEnumDecl(EnumDecl* decl) { if (decl->IsChecked(getCheckedState())) return; // We need to be careful to avoid recursion in the // type-checking logic. We will do the minimal work // to make the type usable in the first phase, and // then check the actual cases in the second phase. // if(this->checkingPhase == CheckingPhase::Header) { // 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 = as(superType)) { // Ignore any erroneous inheritance clauses. continue; } else if(auto declRefType = as(superType)) { 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 integer 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.dynamicCast()) { if(auto enumTypeTypeInterfaceDecl = as(enumTypeTypeDeclRefType->declRef.getDecl())) { 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 witness 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); } } else if( checkingPhase == CheckingPhase::Body ) { auto enumType = DeclRefType::Create( getSession(), makeDeclRef(decl)); auto tagType = decl->tagType; // 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->tagExpr) { // 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 = as(explicitTagVal)) { 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->tagExpr = 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 = as(memberDecl)) continue; // Already checked enum case declarations above. if(auto caseDecl = as(memberDecl)) 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 SemanticsVisitor::visitEnumCaseDecl(EnumCaseDecl* decl) { if (decl->IsChecked(getCheckedState())) return; if(checkingPhase == CheckingPhase::Body) { // An enum case had better appear inside an enum! // // TODO: Do we need/want to support generic cases some day? auto parentEnumDecl = as(decl->ParentDecl); 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->tagExpr) { 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->tagExpr = initExpr; } } decl->SetCheckState(getCheckedState()); } void SemanticsVisitor::visitDeclGroup(DeclGroup* declGroup) { for (auto decl : declGroup->decls) { dispatchDecl(decl); } } void SemanticsVisitor::visitTypeDefDecl(TypeDefDecl* decl) { if (decl->IsChecked(getCheckedState())) return; if (checkingPhase == CheckingPhase::Header) { decl->type = CheckProperType(decl->type); } decl->SetCheckState(getCheckedState()); } void SemanticsVisitor::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 = as(decl->ParentDecl); 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 SemanticsVisitor::visitAssocTypeDecl(AssocTypeDecl* decl) { if (decl->IsChecked(getCheckedState())) return; if (checkingPhase == CheckingPhase::Header) { decl->SetCheckState(DeclCheckState::CheckedHeader); // assoctype only allowed in an interface auto interfaceDecl = as(decl->ParentDecl); 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 SemanticsVisitor::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 SemanticsVisitor::getGenericParams( GenericDecl* decl, List& outParams, List outConstraints) { for (auto dd : decl->Members) { if (dd == decl->inner) continue; if (auto typeParamDecl = as(dd)) outParams.add(typeParamDecl); else if (auto valueParamDecl = as(dd)) outParams.add(valueParamDecl); else if (auto constraintDecl = as(dd)) outConstraints.add(constraintDecl); } } bool SemanticsVisitor::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 `` would 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. Index paramCount = fstParams.getCount(); if (paramCount != sndParams.getCount()) return false; // Now we'll walk through the parameters. for (Index pp = 0; pp < paramCount; ++pp) { Decl* fstParam = fstParams[pp]; Decl* sndParam = sndParams[pp]; if (auto fstTypeParam = as(fstParam)) { if (auto sndTypeParam = as(sndParam)) { // TODO: is there any validation that needs to be performed here? } else { // Type and non-type parameters can't match. return false; } } else if (auto fstValueParam = as(fstParam)) { if (auto sndValueParam = as(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. Index constraintCount = fstConstraints.getCount(); if(constraintCount != sndConstraints.getCount()) return false; for (Index 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; } bool SemanticsVisitor::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.getCount(); auto sndParamCount = sndParams.getCount(); if (fstParamCount != sndParamCount) return false; for (Index 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 SemanticsVisitor::createDummySubstitutions( GenericDecl* genericDecl) { RefPtr subst = new GenericSubstitution(); subst->genericDecl = genericDecl; for (auto dd : genericDecl->Members) { if (dd == genericDecl->inner) continue; if (auto typeParam = as(dd)) { auto type = DeclRefType::Create(getSession(), makeDeclRef(typeParam)); subst->args.add(type); } else if (auto valueParam = as(dd)) { auto val = new GenericParamIntVal( makeDeclRef(valueParam)); subst->args.add(val); } // TODO: need to handle constraints here? } return subst; } void SemanticsVisitor::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 = as(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 = as(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 = as(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 re-declarations. // // 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 redeclaration getSink()->diagnose(funcDecl, Diagnostics::functionRedeclarationWithDifferentReturnType, funcDecl->getName(), resultType, prevResultType); getSink()->diagnose(prevFuncDecl, Diagnostics::seePreviousDeclarationOf, funcDecl->getName()); // 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::functionRedefinition, funcDecl->getName()); getSink()->diagnose(prevFuncDecl, Diagnostics::seePreviousDefinitionOf, funcDecl->getName()); // 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 SemanticsVisitor::visitScopeDecl(ScopeDecl*) { // Nothing to do } void SemanticsVisitor::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...). // A default argument value should not be allowed on an // `out` or `inout` parameter. // // TODO: we could relax this by requiring the expression // to yield an lvalue, but that seems like a feature // with limited practical utility (and an easy source // of confusing behavior). // // Note: the `InOutModifier` class inherits from `OutModifier`, // so we only need to check for the base case. // if(paramDecl->FindModifier()) { getSink()->diagnose(initExpr, Diagnostics::outputParameterCannotHaveDefaultValue); } } paramDecl->SetCheckState(DeclCheckState::Checked); } void SemanticsVisitor::VisitFunctionDeclaration(FuncDecl *functionNode) { if (functionNode->IsChecked(DeclCheckState::CheckedHeader)) return; functionNode->SetCheckState(DeclCheckState::CheckingHeader); auto oldFunc = this->function; this->function = functionNode; auto resultType = functionNode->ReturnType; if(resultType.exp) { resultType = CheckProperType(functionNode->ReturnType); } else { resultType = TypeExp(getSession()->getVoidType()); } functionNode->ReturnType = resultType; 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); } IntegerLiteralValue SemanticsVisitor::GetMinBound(RefPtr val) { if (auto constantVal = as(val)) return constantVal->value; // TODO(tfoley): Need to track intervals so that this isn't just a lie... return 1; } void SemanticsVisitor::maybeInferArraySizeForVariable(VarDeclBase* varDecl) { // Not an array? auto arrayType = as(varDecl->type); 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 type of the initializer an array type? if(auto arrayInitType = as(initExpr->type)) { 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 SemanticsVisitor::validateArraySizeForVariable(VarDeclBase* varDecl) { auto arrayType = as(varDecl->type); 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 SemanticsVisitor::visitVarDecl(VarDecl* varDecl) { CheckVarDeclCommon(varDecl); } void SemanticsVisitor::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 = as(decl->targetType)) { // 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 SemanticsVisitor::visitExtensionDecl(ExtensionDecl* decl) { if (decl->IsChecked(getCheckedState())) return; if (!as(decl->targetType)) { 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()); } RefPtr SemanticsVisitor::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 = as(parent); if (genericParent) { parent = genericParent->ParentDecl; } // Now look at the type of the parent (or grandparent). if (auto aggTypeDecl = as(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 = as(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 SemanticsVisitor::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 SemanticsVisitor::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 SemanticsVisitor::visitAccessorDecl(AccessorDecl* decl) { if (checkingPhase == CheckingPhase::Header) { // An accessor 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 = as(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()); } GenericDecl* SemanticsVisitor::GetOuterGeneric(Decl* decl) { auto parentDecl = decl->ParentDecl; if (!parentDecl) return nullptr; auto parentGeneric = as(parentDecl); return parentGeneric; } DeclRef SemanticsVisitor::ApplyExtensionToType( ExtensionDecl* extDecl, RefPtr type) { DeclRef extDeclRef = makeDeclRef(extDecl); // If the extension is a generic extension, then we // need to infer type arguments 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(); } // Construct 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 = as(targetType)) { 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 = as(type)) { 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; } QualType SemanticsVisitor::GetTypeForDeclRef(DeclRef declRef) { return getTypeForDeclRef( getSession(), this, getSink(), declRef, &typeResult); } void SemanticsVisitor::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 SemanticsVisitor::visitEmptyDecl(EmptyDecl* /*decl*/) { // nothing to do } void SemanticsVisitor::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 importedModule = findOrImportModule( getLinkage(), name, decl->moduleNameAndLoc.loc, getSink()); // If we didn't find a matching module, then bail out if (!importedModule) return; // Record the module that was imported, so that we can use // it later during code generation. auto importedModuleDecl = importedModule->getModuleDecl(); decl->importedModuleDecl = importedModuleDecl; // Add the declarations from the imported module into the scope // that the `import` declaration is set to extend. // importModuleIntoScope(scope.Ptr(), importedModuleDecl); // Record the `import`ed module (and everything it depends on) // as a dependency of the module we are compiling. if(auto module = getModule(decl)) { module->addModuleDependency(importedModule); } decl->SetCheckState(getCheckedState()); } }