diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/slang-check-conformance.cpp | 6 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 1414 | ||||
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 37 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 391 | ||||
| -rw-r--r-- | source/slang/slang-check-modifier.cpp | 8 | ||||
| -rw-r--r-- | source/slang/slang-check-overload.cpp | 10 | ||||
| -rw-r--r-- | source/slang/slang-check-shader.cpp | 15 | ||||
| -rw-r--r-- | source/slang/slang-check-stmt.cpp | 144 | ||||
| -rw-r--r-- | source/slang/slang-check-type.cpp | 11 | ||||
| -rw-r--r-- | source/slang/slang-check.cpp | 31 | ||||
| -rw-r--r-- | source/slang/slang-check.h | 1 | ||||
| -rw-r--r-- | source/slang/slang-lookup.cpp | 20 | ||||
| -rw-r--r-- | source/slang/slang-lookup.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-syntax-base-defs.h | 6 | ||||
| -rw-r--r-- | source/slang/slang-syntax.cpp | 10 | ||||
| -rw-r--r-- | source/slang/slang-syntax.h | 119 |
16 files changed, 1242 insertions, 983 deletions
diff --git a/source/slang/slang-check-conformance.cpp b/source/slang/slang-check-conformance.cpp index 81d36c6f6..ce41df521 100644 --- a/source/slang/slang-check-conformance.cpp +++ b/source/slang/slang-check-conformance.cpp @@ -173,11 +173,11 @@ namespace Slang if( auto aggTypeDeclRef = declRef.as<AggTypeDecl>() ) { - checkDecl(aggTypeDeclRef.getDecl()); + ensureDecl(aggTypeDeclRef, DeclCheckState::CanEnumerateBases); for( auto inheritanceDeclRef : getMembersOfTypeWithExt<InheritanceDecl>(aggTypeDeclRef)) { - checkDecl(inheritanceDeclRef.getDecl()); + ensureDecl(inheritanceDeclRef, DeclCheckState::CanUseBaseOfInheritanceDecl); // Here we will recursively look up conformance on the type // that is being inherited from. This is dangerous because @@ -210,7 +210,7 @@ namespace Slang // if an inheritance decl is not found, try to find a GenericTypeConstraintDecl for (auto genConstraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(aggTypeDeclRef)) { - checkDecl(genConstraintDeclRef.getDecl()); + ensureDecl(genConstraintDeclRef, DeclCheckState::CanUseBaseOfInheritanceDecl); auto inheritedType = GetSup(genConstraintDeclRef); TypeWitnessBreadcrumb breadcrumb; breadcrumb.prev = inBreadcrumbs; diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 581cefdcb..879f1c5fa 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -14,6 +14,114 @@ namespace Slang { + /// Visitor to transition declarations to `DeclCheckState::CheckedModifiers` + struct SemanticsDeclModifiersVisitor + : public SemanticsDeclVisitorBase + , public DeclVisitor<SemanticsDeclModifiersVisitor> + { + SemanticsDeclModifiersVisitor(SharedSemanticsContext* shared) + : SemanticsDeclVisitorBase(shared) + {} + + void visitDeclGroup(DeclGroup*) {} + + void visitDecl(Decl* decl) + { + checkModifiers(decl); + } + }; + + struct SemanticsDeclHeaderVisitor + : public SemanticsDeclVisitorBase + , public DeclVisitor<SemanticsDeclHeaderVisitor> + { + SemanticsDeclHeaderVisitor(SharedSemanticsContext* shared) + : SemanticsDeclVisitorBase(shared) + {} + + void visitDecl(Decl*) {} + void visitDeclGroup(DeclGroup*) {} + + void checkVarDeclCommon(RefPtr<VarDeclBase> varDecl); + + void visitVarDecl(VarDecl* varDecl) + { + checkVarDeclCommon(varDecl); + } + + void visitImportDecl(ImportDecl* decl); + + void visitGenericTypeParamDecl(GenericTypeParamDecl* decl); + + void visitGenericValueParamDecl(GenericValueParamDecl* decl); + + void visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl); + + void visitGenericDecl(GenericDecl* genericDecl); + + void visitTypeDefDecl(TypeDefDecl* decl); + + void visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl); + + void visitAssocTypeDecl(AssocTypeDecl* decl); + + void visitFuncDecl(FuncDecl* funcDecl); + + void visitParamDecl(ParamDecl* paramDecl); + + void visitConstructorDecl(ConstructorDecl* decl); + + void visitSubscriptDecl(SubscriptDecl* decl); + + void visitAccessorDecl(AccessorDecl* decl); + }; + + struct SemanticsDeclBasesVisitor + : public SemanticsDeclVisitorBase + , public DeclVisitor<SemanticsDeclBasesVisitor> + { + SemanticsDeclBasesVisitor(SharedSemanticsContext* shared) + : SemanticsDeclVisitorBase(shared) + {} + + void visitDecl(Decl*) {} + void visitDeclGroup(DeclGroup*) {} + + void visitInheritanceDecl(InheritanceDecl* inheritanceDecl); + + void visitAggTypeDecl(AggTypeDecl* decl); + + void visitEnumDecl(EnumDecl* decl); + + void visitExtensionDecl(ExtensionDecl* decl); + }; + + struct SemanticsDeclBodyVisitor + : public SemanticsDeclVisitorBase + , public DeclVisitor<SemanticsDeclBodyVisitor> + { + SemanticsDeclBodyVisitor(SharedSemanticsContext* shared) + : SemanticsDeclVisitorBase(shared) + {} + + void visitDecl(Decl*) {} + void visitDeclGroup(DeclGroup*) {} + + void checkVarDeclCommon(RefPtr<VarDeclBase> varDecl); + + void visitVarDecl(VarDecl* varDecl) + { + checkVarDeclCommon(varDecl); + } + + void visitEnumCaseDecl(EnumCaseDecl* decl); + + void visitEnumDecl(EnumDecl* decl); + + void visitFuncDecl(FuncDecl* funcDecl); + + void visitParamDecl(ParamDecl* paramDecl); + }; /// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration? bool isEffectivelyStatic( @@ -104,6 +212,22 @@ namespace Slang return true; } + static bool _isLocalVar(VarDeclBase* varDecl) + { + auto pp = varDecl->ParentDecl; + + if(as<ScopeDecl>(pp)) + return true; + + if(auto genericDecl = as<GenericDecl>(pp)) + pp = genericDecl; + + if(as<FuncDecl>(pp)) + return true; + + return false; + } + // Get the type to use when referencing a declaration QualType getTypeForDeclRef( Session* session, @@ -114,7 +238,40 @@ namespace Slang { if( sema ) { - sema->checkDecl(declRef.getDecl()); + // 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. + // + // We detect the problematic case by looking for an attempt to reference + // a local variable declaration when it is unchecked, or in the process + // of being checked (the latter case catches a local variable that refers + // to itself in its initial-value expression). + // + auto checkStateExt = declRef.getDecl()->checkState; + if( checkStateExt.getState() == DeclCheckState::Unchecked + || checkStateExt.isBeingChecked() ) + { + if(auto varDecl = as<VarDecl>(declRef.getDecl())) + { + if(_isLocalVar(varDecl)) + { + sema->getSink()->diagnose(varDecl, Diagnostics::localVariableUsedBeforeDeclared, varDecl); + return QualType(session->getErrorType()); + } + } + } + + // Once we've rules out the case of referencing a local declaration + // before it has been checked, we will go ahead and ensure that + // semantic checking has been performed on the chosen declaration, + // at least up to the point where we can query its type. + // + sema->ensureDecl(declRef, DeclCheckState::CanUseTypeOfValueDecl); } // We need to insert an appropriate type for the expression, based on @@ -313,9 +470,9 @@ namespace Slang return subst; } - void checkDecl(SemanticsVisitor* visitor, Decl* decl) + void ensureDecl(SemanticsVisitor* visitor, Decl* decl, DeclCheckState state) { - visitor->checkDecl(decl); + visitor->ensureDecl(decl, state); } bool SemanticsVisitor::isDeclUsableAsStaticMember( @@ -381,13 +538,28 @@ namespace Slang return isDeclUsableAsStaticMember(decl); } + /// Dispatch an appropriate visitor to check `decl` up to state `state` + /// + /// The current state of `decl` must be `state-1`. + /// This call does *not* handle updating the state of `decl`; the + /// caller takes responsibility for doing so. + /// + static void _dispatchDeclCheckingVisitor(Decl* decl, DeclCheckState state, SharedSemanticsContext* shared); + // 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> decl, DeclCheckState state) + void SemanticsVisitor::ensureDecl(Decl* decl, DeclCheckState state) { + // If the `decl` has already been checked up to or beyond `state` + // then there is nothing for us to do. + // if (decl->IsChecked(state)) return; - if (decl->checkState == DeclCheckState::CheckingHeader) + + // Is the declaration already being checked, somewhere up the + // call stack from us? + // + if(decl->checkState.isBeingChecked()) { // We tried to reference the same declaration while checking it! // @@ -399,58 +571,130 @@ namespace Slang 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. + // Set the flag that indicates we are checking this declaration, + // so that the cycle check above will catch us before we go + // into any infinite loops. + // + decl->checkState.setIsBeingChecked(true); + + // Our task is to bring the `decl` up to `state` which may be + // one or more steps ahead of where it currently is. We can + // invoke a visitor designed to bring a declaration from state + // N to state N+1, and in general we might need multiple such + // passes to get `decl` to where we need it. // - // 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. + // The coding of this loop is somewhat defensive to deal + // with special cases that will be described along the way. // - if (auto varDecl = as<VarDecl>(decl)) + for(;;) { - if (auto parenScope = as<ScopeDecl>(varDecl->ParentDecl)) + // The first thing is to check what state the decl is + // currently in at the start of this loop iteration, + // and to bail out if it has been checked up to + // (or beyond) our target state. + // + auto currentState = decl->checkState.getState(); + if(currentState >= state) + break; + + // Because our visitors are only designed to go from state + // N to N+1 in general, we will aspire to transition to + // a state that is one greater than `currentState`. + // + auto nextState = DeclCheckState(Int(currentState) + 1); + + // We now dispatch an appropriate visitor based on `nextState`. + // + _dispatchDeclCheckingVisitor(decl, nextState, getShared()); + + // In the common case, the visitor will have done the necessary + // checking, but will *not* have updated the `checkState` on + // `decl`. In that case we will do the update here, to save + // us the complication of having to deal with state update in + // every single visitor method. + // + // However, sometimes a visitor *will* want to manually update + // the state of a declaration, and it may actually update it + // *past* the `nextState` we asked for (or even past the + // eventual target `state`). In those cases we don't want to + // accidentally set the state of `decl` to something lower + // than what has actually been checked, so we test for + // such cases here. + // + if(nextState > decl->checkState.getState()) { - // 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; + decl->SetCheckState(nextState); } } - if (DeclCheckState::CheckingHeader > decl->checkState) - { - decl->SetCheckState(DeclCheckState::CheckingHeader); - } + // Once we are done here, the state of `decl` should have + // been upgraded to (at least) `state`. + // + SLANG_ASSERT(decl->IsChecked(state)); - // Check the modifiers on the declaration first, in case - // semantics of the body itself will depend on them. - checkModifiers(decl); + // Now that we are done checking `decl` we need to restore + // its "is being checked" flag so that we don't generate + // errors the next time somebody calls `ensureDecl()` on it. + // + decl->checkState.setIsBeingChecked(false); + } + + /// Recursively ensure the tree of declarations under `decl` is in `state`. + /// + /// This function does *not* handle declarations nested in function bodies + /// because those cannot be meaningfully checked outside of the context + /// of their surrounding statement(s). + /// + static void _ensureAllDeclsRec( + SemanticsDeclVisitorBase* visitor, + Decl* decl, + DeclCheckState state) + { + // Ensure `decl` itself first. + visitor->ensureDecl(decl, state); + + // If `decl` is a container, then we want to ensure its children. + if(auto containerDecl = as<ContainerDecl>(decl)) + { + // As an exception, if any of the child is a `ScopeDecl`, + // then that indicates that it represents a scope for local + // declarations under a statement (e.g., in a function body), + // and we don't want to check such local declarations here. + // + for(auto childDecl : containerDecl->Members) + { + if(as<ScopeDecl>(childDecl)) + continue; - // Use visitor pattern to dispatch to correct case - dispatchDecl(decl); + _ensureAllDeclsRec(visitor, childDecl, state); + } + } - if(state > decl->checkState) + // Note: the "inner" declaration of a `GenericDecl` is currently + // not exposed as one of its children (despite a `GenericDecl` + // being a `ContainerDecl`), so we need to handle the inner + // declaration of a generic as another case here. + // + if(auto genericDecl = as<GenericDecl>(decl)) { - decl->SetCheckState(state); + _ensureAllDeclsRec(visitor, genericDecl->inner, state); } } - void SemanticsVisitor::EnusreAllDeclsRec(RefPtr<Decl> decl) + static bool isUnsizedArrayType(Type* type) { - checkDecl(decl); - if (auto containerDecl = as<ContainerDecl>(decl)) - { - for (auto m : containerDecl->Members) - { - EnusreAllDeclsRec(m); - } - } + // Not an array? + auto arrayType = as<ArrayExpressionType>(type); + if (!arrayType) return false; + + // Explicit element count given? + auto elementCount = arrayType->ArrayLength; + if (elementCount) return true; + + return true; } - void SemanticsVisitor::CheckVarDeclCommon(RefPtr<VarDeclBase> varDecl) + void SemanticsDeclHeaderVisitor::checkVarDeclCommon(RefPtr<VarDeclBase> varDecl) { // A variable that didn't have an explicit type written must // have its type inferred from the initial-value expression. @@ -481,21 +725,29 @@ namespace Slang varDecl->type.type = initExpr->type; } + // If we've gone down this path, then the variable + // declaration is actually pretty far along in checking varDecl->SetCheckState(DeclCheckState::Checked); } else { - if (function || checkingPhase == CheckingPhase::Header) + // A variable with an explicit type is simpler, for the + // most part. + + TypeExp typeExp = CheckUsableType(varDecl->type); + varDecl->type = typeExp; + if (varDecl->type.Equals(getSession()->getVoidType())) { - TypeExp typeExp = CheckUsableType(varDecl->type); - varDecl->type = typeExp; - if (varDecl->type.Equals(getSession()->getVoidType())) - { - getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid); - } + getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid); } - if (checkingPhase == CheckingPhase::Body) + // If this is an unsized 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... + // + if(isUnsizedArrayType(varDecl->type)) { if (auto initExpr = varDecl->initExpr) { @@ -503,25 +755,29 @@ namespace Slang 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(DeclCheckState::Checked); } } + // + // 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); + } + } + void SemanticsDeclBodyVisitor::checkVarDeclCommon(RefPtr<VarDeclBase> varDecl) + { + if (auto initExpr = varDecl->initExpr) + { + initExpr = CheckTerm(initExpr); + initExpr = coerce(varDecl->type.Ptr(), initExpr); + varDecl->initExpr = initExpr; } - varDecl->SetCheckState(getCheckedState()); } // Fill in default substitutions for the 'subtype' part of a type constraint decl @@ -539,76 +795,55 @@ namespace Slang } } - void SemanticsVisitor::CheckGenericConstraintDecl(GenericTypeConstraintDecl* decl) + void SemanticsDeclHeaderVisitor::visitGenericTypeConstraintDecl(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); - } + // + CheckConstraintSubType(decl->sub); + decl->sub = TranslateTypeNodeForced(decl->sub); + decl->sup = TranslateTypeNodeForced(decl->sup); } - void SemanticsVisitor::checkDecl(Decl* decl) + void SemanticsDeclHeaderVisitor::visitGenericTypeParamDecl(GenericTypeParamDecl* decl) { - EnsureDecl(decl, checkingPhase == CheckingPhase::Header ? DeclCheckState::CheckedHeader : DeclCheckState::Checked); + // TODO: could probably push checking the default value + // for a generic type parameter later. + // + decl->initType = CheckProperType(decl->initType); } - void SemanticsVisitor::checkGenericDeclHeader(GenericDecl* genericDecl) + void SemanticsDeclHeaderVisitor::visitGenericValueParamDecl(GenericValueParamDecl* decl) { - if (genericDecl->IsChecked(DeclCheckState::CheckedHeader)) - return; - // check the parameters + checkVarDeclCommon(decl); + } + + void SemanticsDeclHeaderVisitor::visitGenericDecl(GenericDecl* genericDecl) + { + genericDecl->SetCheckState(DeclCheckState::ReadyForLookup); + for (auto m : genericDecl->Members) { if (auto typeParam = as<GenericTypeParamDecl>(m)) { - typeParam->initType = CheckProperType(typeParam->initType); + ensureDecl(typeParam, DeclCheckState::ReadyForReference); } else if (auto valParam = as<GenericValueParamDecl>(m)) { - // TODO: some real checking here... - CheckVarDeclCommon(valParam); + ensureDecl(valParam, DeclCheckState::ReadyForReference); } else if (auto constraint = as<GenericTypeConstraintDecl>(m)) { - CheckGenericConstraintDecl(constraint); + ensureDecl(constraint, DeclCheckState::ReadyForReference); } } - - 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) + void SemanticsDeclBasesVisitor::visitInheritanceDecl(InheritanceDecl* inheritanceDecl) { - 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); @@ -636,172 +871,182 @@ namespace Slang 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*) + // Concretize interface conformances so that we have witnesses as required for lookup. + // for lookup. + struct SemanticsDeclConformancesVisitor + : public SemanticsDeclVisitorBase + , public DeclVisitor<SemanticsDeclConformancesVisitor> { - // These are only used in the stdlib, so no checking is needed for now - } + SemanticsDeclConformancesVisitor(SharedSemanticsContext* shared) + : SemanticsDeclVisitorBase(shared) + {} - void SemanticsVisitor::visitGenericValueParamDecl(GenericValueParamDecl*) - { - // These are only used in the stdlib, so no checking is needed for now - } + void visitDecl(Decl*) {} + void visitDeclGroup(DeclGroup*) {} - void SemanticsVisitor::checkInterfaceConformancesRec(Decl* decl) - { // Any user-defined type may have declared interface conformances, // which we should check. // - if( auto aggTypeDecl = as<AggTypeDecl>(decl) ) + void visitAggTypeDecl(AggTypeDecl* aggTypeDecl) { 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<ExtensionDecl>(decl)) + void visitExtensionDecl(ExtensionDecl* extensionDecl) { 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<GenericDecl>(decl)) + /// Recursively register any builtin declarations that need to be attached to the `session`. + /// + /// This function should only be needed for declarations in the standard library. + /// + static void _registerBuiltinDeclsRec(Session* session, Decl* decl) + { + if (auto builtinMod = decl->FindModifier<BuiltinTypeModifier>()) { - checkInterfaceConformancesRec(genericDecl->inner); + registerBuiltinDecl(session, decl, builtinMod); } - // 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<ContainerDecl>(decl)) + if (auto magicMod = decl->FindModifier<MagicTypeModifier>()) { - for(auto member : containerDecl->Members) - { - checkInterfaceConformancesRec(member); - } + registerMagicDecl(session, decl, magicMod); } - } - void SemanticsVisitor::visitModuleDecl(ModuleDecl* programNode) - { - // Try to register all the builtin decls - for (auto decl : programNode->Members) + if(auto containerDecl = as<ContainerDecl>(decl)) { - auto inner = decl; - if (auto genericDecl = as<GenericDecl>(decl)) + for(auto childDecl : containerDecl->Members) { - inner = genericDecl->inner; - } + if(as<ScopeDecl>(childDecl)) + continue; - if (auto builtinMod = inner->FindModifier<BuiltinTypeModifier>()) - { - registerBuiltinDecl(getSession(), decl, builtinMod); - } - if (auto magicMod = inner->FindModifier<MagicTypeModifier>()) - { - registerMagicDecl(getSession(), decl, magicMod); + _registerBuiltinDeclsRec(session, childDecl); } } - - // We need/want to visit any `import` declarations before - // anything else, to make sure that scoping works. - for(auto& importDecl : programNode->getMembersOfType<ImportDecl>()) - { - checkDecl(importDecl); - } - // register all extensions - for (auto & s : programNode->getMembersOfType<ExtensionDecl>()) - registerExtension(s); - for (auto & g : programNode->getMembersOfType<GenericDecl>()) + if(auto genericDecl = as<GenericDecl>(decl)) { - if (auto extDecl = as<ExtensionDecl>(g->inner)) - { - checkGenericDeclHeader(g); - registerExtension(extDecl); - } + _registerBuiltinDeclsRec(session, genericDecl->inner); } - // check user defined attribute classes first - for (auto decl : programNode->Members) + } + + void SemanticsDeclVisitorBase::checkModule(ModuleDecl* moduleDecl) + { + // When we are dealing with code from the standard library, + // there is a potential problem where we might need to look + // up built-in types like `Int` through the session (e.g., + // to determine the type for an integer literal), but those + // types might not have been registered yet. We solve that + // by doing a pre-process on standard-library code to find + // and register any built-in declarations. + // + // TODO: This could be factored into another visitor pass + // that fits the more standard checking below, but that would + // seemingly add overhead to checking things other than + // the standard library. + // + if(isFromStdLib(moduleDecl)) { - if (auto typeMember = as<StructDecl>(decl)) - { - bool isTypeAttributeClass = false; - for (auto attrib : typeMember->GetModifiersOfType<UncheckedAttribute>()) - { - if (attrib->name == getSession()->getNameObj("AttributeUsageAttribute")) - { - isTypeAttributeClass = true; - break; - } - } - if (isTypeAttributeClass) - checkDecl(decl); - } + _registerBuiltinDeclsRec(getSession(), moduleDecl); } - // check types - for (auto & s : programNode->getMembersOfType<TypeDefDecl>()) - checkDecl(s.Ptr()); - for (int pass = 0; pass < 2; pass++) + // We need/want to visit any `import` declarations before + // anything else, to make sure that scoping works. + // + // TODO: This could be factored into another visitor pass + // that fits more with the standard checking below. + // + for(auto& importDecl : moduleDecl->getMembersOfType<ImportDecl>()) { - checkingPhase = pass == 0 ? CheckingPhase::Header : CheckingPhase::Body; - - for (auto & s : programNode->getMembersOfType<AggTypeDecl>()) - { - checkDecl(s.Ptr()); - } - // HACK(tfoley): Visiting all generic declarations here, - // because otherwise they won't get visited. - for (auto & g : programNode->getMembersOfType<GenericDecl>()) - { - 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<ExtensionDecl>()) - checkDecl(s); - - for (auto & func : programNode->getMembersOfType<FuncDecl>()) - { - if (!func->IsChecked(getCheckedState())) - { - VisitFunctionDeclaration(func.Ptr()); - } - } - for (auto & func : programNode->getMembersOfType<FuncDecl>()) - { - 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); - } + ensureDecl(importDecl, DeclCheckState::Checked); + } - if (pass == 0) - { - checkInterfaceConformancesRec(programNode); - } + // The entire goal of semantic checking is to get all of the + // declarations in the module up to `DeclCheckState::Checked`. + // + // The main catch is that checking one declaration A up to state M + // may required that declaration B is checked up to state N. + // A call to `ensureDecl(B, N)` can guarantee that things are checked + // when and where we need them, but that runs the risk of creating + // very deep recursion in the semantic checking. + // + // Instead, we would rather do more breadth-first checking, + // where everything gets checked up to state 1, 2, ... + // before anything gets too far ahead. + // We will therefore enumerate the states/phases for checking, + // and then iteratively try to update all declarations to each + // state in turn. + // + // Note: for a simpler language we could eliminate `ensureDecl` + // completely and *just* have these phases of checking. + // Unfortunately, we have some circularity between the phases: + // + // * Checking an overloaded call requires knowing the parameter + // types of all candidate callees. + // + // * Checking the parameter type of a function requires being + // able to check type expressions. + // + // * A type expression like `vector<T, N>` may have an arbitary + // expression for `N`. + // + // * An arbitrary expression may include function calls, which + // may be to overloaded functions. + // + // Languages like C++ solve the apparent problem by making + // restrictions on order of declaration/definition (and by + // requiring forward declarations or the `template`/`typename` + // keywrods in some cases). + // + // TODO: We could eventually eliminate the potential recursion + // in checking by splitting each phase into a "requirements gathering" + // step and an actual execution step. + // + // When checking a declaration D up to state S, the requirements + // gathering step would produce a list of pairs `(someDecl, someState)` + // indicating that `someDecl` must be in `someState` before the + // actual execution of checking for `(D,S)` can proceeed. The checker + // can then produce an elaborated dependency graph and select nodes + // for execution in an order that satisfies all the dependencies. + // + // Such a more elaborate checking scheme will have to wait for another + // day, but might be worth it (or even necessary) if/when we want to + // support incremental compilation. + // + DeclCheckState states[] = + { + DeclCheckState::ModifiersChecked, + DeclCheckState::ReadyForReference, + DeclCheckState::ReadyForLookup, + DeclCheckState::ReadyForLookup, + DeclCheckState::Checked + }; + for(auto s : states) + { + // When advancing to state `s` we will recursively + // advance all declarations rooted in the module + // up to `s`. + // + // TODO: In cases where a large module is split across files, + // we could potentially parallelize front-end compilation by + // having multiple instances of the front end where each is + // only responsible for those declarations in a given file. + // + // Under that model, we might only apply later phases of + // checking (notably the final push to `DeclState::Checked`) + // to the subset of declarations coming from a given source + // file. + // + _ensureAllDeclsRec(this, moduleDecl, s); } + + // Once we have completed the above loop, all declarations not + // nested in function bodies should be in `DeclState::Checked`. + // Furthermore, because a fully checked function will have checked + // its body, this also means that all function bodies and the + // declarations they contain should be fully checked. } bool SemanticsVisitor::doesSignatureMatchRequirement( @@ -1004,7 +1249,7 @@ namespace Slang { if(auto requiredTypeDeclRef = requiredMemberDeclRef.as<AssocTypeDecl>()) { - checkDecl(subAggTypeDeclRef.getDecl()); + ensureDecl(subAggTypeDeclRef, DeclCheckState::CanUseAsType); auto satisfyingType = DeclRefType::Create(getSession(), subAggTypeDeclRef); return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable); @@ -1016,7 +1261,7 @@ namespace Slang // check if the specified type satisfies the constraints defined by the associated type if (auto requiredTypeDeclRef = requiredMemberDeclRef.as<AssocTypeDecl>()) { - checkDecl(typedefDeclRef.getDecl()); + ensureDecl(typedefDeclRef, DeclCheckState::CanUseAsType); auto satisfyingType = getNamedType(getSession(), typedefDeclRef); return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable); @@ -1155,7 +1400,8 @@ namespace Slang // We need to check the declaration of the interface // before we can check that we conform to it. - checkDecl(interfaceDeclRef.getDecl()); + // + ensureDecl(interfaceDeclRef, DeclCheckState::CanReadInterfaceRequirements); // We will construct the witness table, and register it // *before* we go about checking fine-grained requirements, @@ -1372,37 +1618,16 @@ namespace Slang } } - void SemanticsVisitor::visitAggTypeDecl(AggTypeDecl* decl) + void SemanticsDeclBasesVisitor::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); + // TODO: We need to enumerate the bases here, + // and ideally form a "class precedence list" + // from them. - // Now check all of the member declarations. - for (auto member : decl->Members) + for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() ) { - checkDecl(member); + ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl); } - decl->SetCheckState(getCheckedState()); } bool SemanticsVisitor::isIntegerBaseType(BaseType baseType) @@ -1439,357 +1664,286 @@ namespace Slang getSink()->diagnose(loc, Diagnostics::invalidEnumTagType, type); } - void SemanticsVisitor::visitEnumDecl(EnumDecl* decl) + void SemanticsDeclBasesVisitor::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<Type> tagType; - InheritanceDecl* tagTypeInheritanceDecl = nullptr; - for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>()) - { - checkDecl(inheritanceDecl); + // 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<Type> tagType; + InheritanceDecl* tagTypeInheritanceDecl = nullptr; + for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>()) + { + ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl); - // Look at the type being inherited from. - auto superType = inheritanceDecl->base.type; + // Look at the type being inherited from. + auto superType = inheritanceDecl->base.type; - if(auto errorType = as<ErrorType>(superType)) + if(auto errorType = as<ErrorType>(superType)) + { + // Ignore any erroneous inheritance clauses. + continue; + } + else if(auto declRefType = as<DeclRefType>(superType)) + { + if(auto interfaceDeclRef = declRefType->declRef.as<InterfaceDecl>()) { - // Ignore any erroneous inheritance clauses. + // Don't consider interface bases as candidates for + // the tag type. continue; } - else if(auto declRefType = as<DeclRefType>(superType)) - { - if(auto interfaceDeclRef = declRefType->declRef.as<InterfaceDecl>()) - { - // 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) + if(tagType) { - tagType = getSession()->getIntType(); + // We already found a tag type. + getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType); + getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType); + break; } 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); + tagType = superType; + tagTypeInheritanceDecl = inheritanceDecl; } - decl->tagType = tagType; + } + // 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<Type> enumTypeType = getSession()->getEnumTypeType(); - - RefPtr<InheritanceDecl> 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> witnessTable = new WitnessTable(); - enumConformanceDecl->witnessTable = witnessTable; - Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag"); - Decl* tagAssociatedTypeDecl = nullptr; - if(auto enumTypeTypeDeclRefType = enumTypeType.dynamicCast<DeclRefType>()) + // 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<Type> enumTypeType = getSession()->getEnumTypeType(); + + RefPtr<InheritanceDecl> 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> witnessTable = new WitnessTable(); + enumConformanceDecl->witnessTable = witnessTable; + + Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag"); + Decl* tagAssociatedTypeDecl = nullptr; + if(auto enumTypeTypeDeclRefType = enumTypeType.dynamicCast<DeclRefType>()) + { + if(auto enumTypeTypeInterfaceDecl = as<InterfaceDecl>(enumTypeTypeDeclRefType->declRef.getDecl())) { - if(auto enumTypeTypeInterfaceDecl = as<InterfaceDecl>(enumTypeTypeDeclRefType->declRef.getDecl())) + for(auto memberDecl : enumTypeTypeInterfaceDecl->Members) { - for(auto memberDecl : enumTypeTypeInterfaceDecl->Members) + if(memberDecl->getName() == tagAssociatedTypeName) { - if(memberDecl->getName() == tagAssociatedTypeName) - { - tagAssociatedTypeDecl = memberDecl; - break; - } + tagAssociatedTypeDecl = memberDecl; + break; } } } - if(!tagAssociatedTypeDecl) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), decl, "failed to find built-in declaration '__Tag'"); - } + } + 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)); + // 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: 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. + // 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); - } + enumConformanceDecl->SetCheckState(DeclCheckState::Checked); } - else if( checkingPhase == CheckingPhase::Body ) - { - auto enumType = DeclRefType::Create( - getSession(), - makeDeclRef(decl)); + } - auto tagType = decl->tagType; + void SemanticsDeclBodyVisitor::visitEnumDecl(EnumDecl* decl) + { + auto enumType = DeclRefType::Create( + getSession(), + makeDeclRef(decl)); - // Check the enum cases in order. - for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>()) - { - // 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; + auto tagType = decl->tagType; - checkDecl(caseDecl); - } + // Check the enum cases in order. + for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>()) + { + // 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. + // + // TODO(tfoley): the case should grab its type when + // doing its own header checking, rather than rely on this... + caseDecl->type.type = enumType; + + ensureDecl(caseDecl, DeclCheckState::Checked); + } - // 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<EnumCaseDecl>()) + // 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<EnumCaseDecl>()) + { + if(auto explicitTagValExpr = caseDecl->tagExpr) { - 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. + // This tag has an initializer, so it should establish + // the tag value for a successor case that doesn't + // provide an explicit tag. - RefPtr<IntVal> explicitTagVal = TryConstantFoldExpr(explicitTagValExpr); - if(explicitTagVal) + RefPtr<IntVal> explicitTagVal = TryConstantFoldExpr(explicitTagValExpr); + if(explicitTagVal) + { + if(auto constIntVal = as<ConstantIntVal>(explicitTagVal)) { - if(auto constIntVal = as<ConstantIntVal>(explicitTagVal)) - { - defaultTag = constIntVal->value; - } - else - { - // TODO: need to handle other possibilities here - getSink()->diagnose(explicitTagValExpr, Diagnostics::unexpectedEnumTagExpr); - } + defaultTag = constIntVal->value; } 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. + // TODO: need to handle other possibilities here + getSink()->diagnose(explicitTagValExpr, Diagnostics::unexpectedEnumTagExpr); } } else { - // This tag has no initializer, so it should use - // the default tag value we are tracking. - RefPtr<IntegerLiteralExpr> tagValExpr = new IntegerLiteralExpr(); - tagValExpr->loc = caseDecl->loc; - tagValExpr->type = QualType(tagType); - tagValExpr->value = defaultTag; - - caseDecl->tagExpr = tagValExpr; + // 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. } - - // 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) + else { - // Already checked inheritance declarations above. - if(auto inheritanceDecl = as<InheritanceDecl>(memberDecl)) - continue; - - // Already checked enum case declarations above. - if(auto caseDecl = as<EnumCaseDecl>(memberDecl)) - continue; + // This tag has no initializer, so it should use + // the default tag value we are tracking. + RefPtr<IntegerLiteralExpr> tagValExpr = new IntegerLiteralExpr(); + tagValExpr->loc = caseDecl->loc; + tagValExpr->type = QualType(tagType); + tagValExpr->value = defaultTag; - // 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); + 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++; } - decl->SetCheckState(getCheckedState()); } - void SemanticsVisitor::visitEnumCaseDecl(EnumCaseDecl* decl) + void SemanticsDeclBodyVisitor::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<EnumDecl>(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); + // An enum case had better appear inside an enum! + // + // TODO: Do we need/want to support generic cases some day? + auto parentEnumDecl = as<EnumDecl>(decl->ParentDecl); + SLANG_ASSERT(parentEnumDecl); - // We want to enforce that this is an integer constant - // expression, but we don't actually care to retain - // the value. - CheckIntegerConstantExpression(initExpr); + // The tag type should have already been set by + // the surrounding `enum` declaration. + auto tagType = parentEnumDecl->tagType; + SLANG_ASSERT(tagType); - decl->tagExpr = initExpr; - } - } + // 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); - decl->SetCheckState(getCheckedState()); - } + // We want to enforce that this is an integer constant + // expression, but we don't actually care to retain + // the value. + CheckIntegerConstantExpression(initExpr); - void SemanticsVisitor::visitDeclGroup(DeclGroup* declGroup) - { - for (auto decl : declGroup->decls) - { - dispatchDecl(decl); + decl->tagExpr = initExpr; } } - void SemanticsVisitor::visitTypeDefDecl(TypeDefDecl* decl) + void SemanticsVisitor::ensureDeclBase(DeclBase* declBase, DeclCheckState state) { - if (decl->IsChecked(getCheckedState())) return; - if (checkingPhase == CheckingPhase::Header) + if(auto decl = as<Decl>(declBase)) { - decl->type = CheckProperType(decl->type); + ensureDecl(decl, state); } - decl->SetCheckState(getCheckedState()); - } - - void SemanticsVisitor::visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl) - { - if (decl->IsChecked(getCheckedState())) return; - if (checkingPhase == CheckingPhase::Header) + else if(auto declGroup = as<DeclGroup>(declBase)) { - decl->SetCheckState(DeclCheckState::CheckedHeader); - // global generic param only allowed in global scope - auto program = as<ModuleDecl>(decl->ParentDecl); - if (!program) - getSink()->diagnose(decl, Slang::Diagnostics::globalGenParamInGlobalScopeOnly); - // Now check all of the member declarations. - for (auto member : decl->Members) + for(auto dd : declGroup->decls) { - checkDecl(member); + ensureDecl(dd, state); } } - decl->SetCheckState(getCheckedState()); + else + { + SLANG_UNEXPECTED("unknown case for declaration"); + } } - void SemanticsVisitor::visitAssocTypeDecl(AssocTypeDecl* decl) + void SemanticsDeclHeaderVisitor::visitTypeDefDecl(TypeDefDecl* decl) { - if (decl->IsChecked(getCheckedState())) return; - if (checkingPhase == CheckingPhase::Header) - { - decl->SetCheckState(DeclCheckState::CheckedHeader); - - // assoctype only allowed in an interface - auto interfaceDecl = as<InterfaceDecl>(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()); + decl->type = CheckProperType(decl->type); } - void SemanticsVisitor::visitFuncDecl(FuncDecl* functionNode) + void SemanticsDeclHeaderVisitor::visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl) { - if (functionNode->IsChecked(getCheckedState())) - return; + // global generic param only allowed in global scope + auto program = as<ModuleDecl>(decl->ParentDecl); + if (!program) + getSink()->diagnose(decl, Slang::Diagnostics::globalGenParamInGlobalScopeOnly); + } - if (checkingPhase == CheckingPhase::Header) - { - VisitFunctionDeclaration(functionNode); - } - // TODO: This should really only set "checked header" - functionNode->SetCheckState(getCheckedState()); + void SemanticsDeclHeaderVisitor::visitAssocTypeDecl(AssocTypeDecl* decl) + { + // assoctype only allowed in an interface + auto interfaceDecl = as<InterfaceDecl>(decl->ParentDecl); + if (!interfaceDecl) + getSink()->diagnose(decl, Slang::Diagnostics::assocTypeInInterfaceOnly); + } - if (checkingPhase == CheckingPhase::Body) + void SemanticsDeclBodyVisitor::visitFuncDecl(FuncDecl* funcDecl) + { + if (auto body = funcDecl->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; - } + checkBodyStmt(body, funcDecl); } } @@ -2189,12 +2343,7 @@ namespace Slang } } - void SemanticsVisitor::visitScopeDecl(ScopeDecl*) - { - // Nothing to do - } - - void SemanticsVisitor::visitParamDecl(ParamDecl* paramDecl) + void SemanticsDeclHeaderVisitor::visitParamDecl(ParamDecl* paramDecl) { // TODO: This logic should be shared with the other cases of // variable declarations. The main reason I am not doing it @@ -2208,8 +2357,11 @@ namespace Slang typeExpr = CheckUsableType(typeExpr); paramDecl->type = typeExpr; } + } - paramDecl->SetCheckState(DeclCheckState::CheckedHeader); + void SemanticsDeclBodyVisitor::visitParamDecl(ParamDecl* paramDecl) + { + auto typeExpr = paramDecl->type; // The "initializer" expression for a parameter represents // a default argument value to use if an explicit one is @@ -2245,33 +2397,26 @@ namespace Slang getSink()->diagnose(initExpr, Diagnostics::outputParameterCannotHaveDefaultValue); } } - - paramDecl->SetCheckState(DeclCheckState::Checked); } - void SemanticsVisitor::VisitFunctionDeclaration(FuncDecl *functionNode) + void SemanticsDeclHeaderVisitor::visitFuncDecl(FuncDecl* funcDecl) { - if (functionNode->IsChecked(DeclCheckState::CheckedHeader)) return; - functionNode->SetCheckState(DeclCheckState::CheckingHeader); - auto oldFunc = this->function; - this->function = functionNode; - - auto resultType = functionNode->ReturnType; + auto resultType = funcDecl->ReturnType; if(resultType.exp) { - resultType = CheckProperType(functionNode->ReturnType); + resultType = CheckProperType(resultType); } else { resultType = TypeExp(getSession()->getVoidType()); } - functionNode->ReturnType = resultType; + funcDecl->ReturnType = resultType; HashSet<Name*> paraNames; - for (auto & para : functionNode->GetParameters()) + for (auto & para : funcDecl->GetParameters()) { - EnsureDecl(para, DeclCheckState::CheckedHeader); + ensureDecl(para, DeclCheckState::ReadyForReference); if (paraNames.Contains(para->getName())) { @@ -2280,11 +2425,9 @@ namespace Slang 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); + ValidateFunctionRedeclaration(funcDecl); } IntegerLiteralValue SemanticsVisitor::GetMinBound(RefPtr<IntVal> val) @@ -2353,21 +2496,9 @@ namespace Slang } } - void SemanticsVisitor::visitVarDecl(VarDecl* varDecl) - { - CheckVarDeclCommon(varDecl); - } - - void SemanticsVisitor::registerExtension(ExtensionDecl* decl) + void SemanticsDeclBasesVisitor::visitExtensionDecl(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<DeclRefType>(decl->targetType)) { @@ -2383,22 +2514,6 @@ namespace Slang getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here"); } - void SemanticsVisitor::visitExtensionDecl(ExtensionDecl* decl) - { - if (decl->IsChecked(getCheckedState())) return; - - if (!as<DeclRefType>(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<Type> SemanticsVisitor::findResultTypeForConstructorDecl(ConstructorDecl* decl) { // We want to look at the parent of the declaration, @@ -2436,35 +2551,23 @@ namespace Slang } } - void SemanticsVisitor::visitConstructorDecl(ConstructorDecl* decl) + void SemanticsDeclHeaderVisitor::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 + for (auto& paramDecl : decl->GetParameters()) { - // TODO(tfoley): check body + ensureDecl(paramDecl, DeclCheckState::CanUseTypeOfValueDecl); } - decl->SetCheckState(getCheckedState()); + + // We need to compute the result tyep for this declaration, + // since it wasn't filled in for us. + decl->ReturnType.type = findResultTypeForConstructorDecl(decl); } - void SemanticsVisitor::visitSubscriptDecl(SubscriptDecl* decl) + void SemanticsDeclHeaderVisitor::visitSubscriptDecl(SubscriptDecl* decl) { - if (decl->IsChecked(getCheckedState())) return; for (auto& paramDecl : decl->GetParameters()) { - paramDecl->type = CheckUsableType(paramDecl->type); + ensureDecl(paramDecl, DeclCheckState::CanUseTypeOfValueDecl); } decl->ReturnType = CheckUsableType(decl->ReturnType); @@ -2494,40 +2597,25 @@ namespace Slang getterDecl->ParentDecl = decl; decl->Members.add(getterDecl); } - - for(auto mm : decl->Members) - { - checkDecl(mm); - } - - decl->SetCheckState(getCheckedState()); } - void SemanticsVisitor::visitAccessorDecl(AccessorDecl* decl) + void SemanticsDeclHeaderVisitor::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<SubscriptDecl>(parent)) { - // 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<SubscriptDecl>(parent)) - { - decl->ReturnType = parentSubscript->ReturnType; - } - // TODO: when we add "property" declarations, check for them here - else - { - getSink()->diagnose(decl, Diagnostics::accessorMustBeInsideSubscriptOrProperty); - } - + ensureDecl(parentSubscript, DeclCheckState::CanUseTypeOfValueDecl); + decl->ReturnType = parentSubscript->ReturnType; } + // TODO: when we add "property" declarations, check for them here else { - // TODO: check the body! + getSink()->diagnose(decl, Diagnostics::accessorMustBeInsideSubscriptOrProperty); } - decl->SetCheckState(getCheckedState()); } GenericDecl* SemanticsVisitor::GetOuterGeneric(Decl* decl) @@ -2655,6 +2743,7 @@ namespace Slang QualType SemanticsVisitor::GetTypeForDeclRef(DeclRef<Decl> declRef) { + RefPtr<Type> typeResult; return getTypeForDeclRef( getSession(), this, @@ -2667,6 +2756,7 @@ namespace Slang { // If we've imported this one already, then // skip the step where we modify the current scope. + auto& importedModules = getShared()->importedModules; if (importedModules.Contains(moduleDecl)) { return; @@ -2693,16 +2783,8 @@ namespace Slang } } - void SemanticsVisitor::visitEmptyDecl(EmptyDecl* /*decl*/) - { - // nothing to do - } - - void SemanticsVisitor::visitImportDecl(ImportDecl* decl) + void SemanticsDeclHeaderVisitor::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 @@ -2738,8 +2820,32 @@ namespace Slang { module->addModuleDependency(importedModule); } + } + + static void _dispatchDeclCheckingVisitor(Decl* decl, DeclCheckState state, SharedSemanticsContext* shared) + { + switch(state) + { + case DeclCheckState::ModifiersChecked: + SemanticsDeclModifiersVisitor(shared).dispatch(decl); + break; + + case DeclCheckState::ReadyForReference: + SemanticsDeclHeaderVisitor(shared).dispatch(decl); + break; - decl->SetCheckState(getCheckedState()); + case DeclCheckState::ReadyForLookup: + SemanticsDeclBasesVisitor(shared).dispatch(decl); + break; + + case DeclCheckState::ReadyForConformances: + SemanticsDeclConformancesVisitor(shared).dispatch(decl); + break; + + case DeclCheckState::Checked: + SemanticsDeclBodyVisitor(shared).dispatch(decl); + break; + } } } diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index b9400f34a..9aa081e1b 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -416,7 +416,9 @@ namespace Slang RefPtr<Expr> SemanticsVisitor::CheckTerm(RefPtr<Expr> term) { if (!term) return nullptr; - return ExprVisitor::dispatch(term); + + SemanticsExprVisitor exprVisitor(getShared()); + return exprVisitor.dispatch(term); } RefPtr<Expr> SemanticsVisitor::CreateErrorExpr(Expr* expr) @@ -448,13 +450,13 @@ namespace Slang return nullptr; } - RefPtr<Expr> SemanticsVisitor::visitBoolLiteralExpr(BoolLiteralExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitBoolLiteralExpr(BoolLiteralExpr* expr) { expr->type = getSession()->getBoolType(); return expr; } - RefPtr<Expr> SemanticsVisitor::visitIntegerLiteralExpr(IntegerLiteralExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::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. @@ -474,7 +476,7 @@ namespace Slang return expr; } - RefPtr<Expr> SemanticsVisitor::visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) { if(!expr->type.type) { @@ -483,7 +485,7 @@ namespace Slang return expr; } - RefPtr<Expr> SemanticsVisitor::visitStringLiteralExpr(StringLiteralExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitStringLiteralExpr(StringLiteralExpr* expr) { expr->type = getSession()->getStringType(); return expr; @@ -766,7 +768,7 @@ namespace Slang return subscriptExpr; } - RefPtr<Expr> SemanticsVisitor::visitIndexExpr(IndexExpr* subscriptExpr) + RefPtr<Expr> SemanticsExprVisitor::visitIndexExpr(IndexExpr* subscriptExpr) { auto baseExpr = subscriptExpr->BaseExpression; baseExpr = CheckExpr(baseExpr); @@ -804,7 +806,6 @@ namespace Slang elementType, elementCount); - typeResult = arrayType; subscriptExpr->type = QualType(getTypeType(arrayType)); return subscriptExpr; } @@ -873,7 +874,7 @@ namespace Slang } } - RefPtr<Expr> SemanticsVisitor::visitParenExpr(ParenExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitParenExpr(ParenExpr* expr) { auto base = expr->base; base = CheckTerm(base); @@ -920,7 +921,7 @@ namespace Slang } } - RefPtr<Expr> SemanticsVisitor::visitAssignExpr(AssignExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitAssignExpr(AssignExpr* expr) { expr->left = CheckExpr(expr->left); @@ -1032,7 +1033,7 @@ namespace Slang return rs; } - RefPtr<Expr> SemanticsVisitor::visitInvokeExpr(InvokeExpr *expr) + RefPtr<Expr> SemanticsExprVisitor::visitInvokeExpr(InvokeExpr *expr) { // check the base expression first expr->FunctionExpr = CheckExpr(expr->FunctionExpr); @@ -1045,7 +1046,7 @@ namespace Slang return CheckInvokeExprWithCheckedOperands(expr); } - RefPtr<Expr> SemanticsVisitor::visitVarExpr(VarExpr *expr) + RefPtr<Expr> SemanticsExprVisitor::visitVarExpr(VarExpr *expr) { // If we've already resolved this expression, don't try again. if (expr->declRef) @@ -1068,7 +1069,7 @@ namespace Slang return expr; } - RefPtr<Expr> SemanticsVisitor::visitTypeCastExpr(TypeCastExpr * expr) + RefPtr<Expr> SemanticsExprVisitor::visitTypeCastExpr(TypeCastExpr * expr) { // Check the term we are applying first auto funcExpr = expr->FunctionExpr; @@ -1421,7 +1422,7 @@ namespace Slang return lookupMemberResultFailure(expr, baseType); } - RefPtr<Expr> SemanticsVisitor::visitStaticMemberExpr(StaticMemberExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitStaticMemberExpr(StaticMemberExpr* expr) { expr->BaseExpression = CheckExpr(expr->BaseExpression); @@ -1452,7 +1453,7 @@ namespace Slang return expr; } - RefPtr<Expr> SemanticsVisitor::visitMemberExpr(MemberExpr * expr) + RefPtr<Expr> SemanticsExprVisitor::visitMemberExpr(MemberExpr * expr) { expr->BaseExpression = CheckExpr(expr->BaseExpression); @@ -1521,7 +1522,7 @@ namespace Slang } } - RefPtr<Expr> SemanticsVisitor::visitInitializerListExpr(InitializerListExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::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 @@ -1539,7 +1540,7 @@ namespace Slang // Perform semantic checking of an object-oriented `this` // expression. - RefPtr<Expr> SemanticsVisitor::visitThisExpr(ThisExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitThisExpr(ThisExpr* expr) { // A `this` expression will default to immutable. expr->type.IsLeftValue = false; @@ -1561,7 +1562,7 @@ namespace Slang } else if (auto aggTypeDecl = as<AggTypeDecl>(containerDecl)) { - checkDecl(aggTypeDecl); + ensureDecl(aggTypeDecl, DeclCheckState::CanUseAsType); // Okay, we are using `this` in the context of an // aggregate type, so the expression should be @@ -1573,7 +1574,7 @@ namespace Slang } else if (auto extensionDecl = as<ExtensionDecl>(containerDecl)) { - checkDecl(extensionDecl); + ensureDecl(extensionDecl, DeclCheckState::CanUseExtensionTargetType); // When `this` is used in the context of an `extension` // declaration, then it should refer to an instance of diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 0cc9eb628..7027c4671 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -198,25 +198,9 @@ namespace Slang Dictionary<BasicTypeKeyPair, ConversionCost> conversionCostCache; }; - enum class CheckingPhase + /// Shared state for a semantics-checking session. + struct SharedSemanticsContext { - Header, Body - }; - - struct SemanticsVisitor - : ExprVisitor<SemanticsVisitor, RefPtr<Expr>> - , StmtVisitor<SemanticsVisitor> - , DeclVisitor<SemanticsVisitor> - { - CheckingPhase checkingPhase = CheckingPhase::Header; - DeclCheckState getCheckedState() - { - if (checkingPhase == CheckingPhase::Body) - return DeclCheckState::Checked; - else - return DeclCheckState::CheckedHeader; - } - Linkage* m_linkage = nullptr; DiagnosticSink* m_sink = nullptr; @@ -225,13 +209,6 @@ namespace Slang return m_sink; } -// ModuleDecl * program = nullptr; - FuncDecl * function = nullptr; - - - // lexical outer statements - List<Stmt*> outerStmts; - // We need to track what has been `import`ed, // to avoid importing the same thing more than once // @@ -240,7 +217,7 @@ namespace Slang HashSet<ModuleDecl*> importedModules; public: - SemanticsVisitor( + SharedSemanticsContext( Linkage* linkage, DiagnosticSink* sink) : m_linkage(linkage) @@ -252,11 +229,47 @@ namespace Slang return m_linkage->getSessionImpl(); } + }; + + struct SemanticsVisitor + { + SemanticsVisitor( + SharedSemanticsContext* shared) + : m_shared(shared) + {} + + SharedSemanticsContext* m_shared = nullptr; + + SharedSemanticsContext* getShared() { return m_shared; } + + DiagnosticSink* getSink() { return m_shared->getSink(); } + + Session* getSession() { return m_shared->getSession(); } + + Linkage* getLinkage() { return m_shared->m_linkage; } + NamePool* getNamePool() { return getLinkage()->getNamePool(); } + + /// Information for tracking one or more outer statements. + /// + /// During checking of statements, we need to track what + /// outer statements are in scope, so that we can resolve + /// the target for a `break` or `continue` statement (and + /// validate that such statements are only used in contexts + /// where such a target exists). + /// + /// We use a linked list of `OuterStmtInfo` threaded up + /// through the recursive call stack to track the statements + /// that are lexically surrounding the one we are checking. + /// + struct OuterStmtInfo + { + Stmt* stmt; + OuterStmtInfo* next; + }; + public: // Translate Types - // TODO(tfoley): What is this and why is it needed? - RefPtr<Type> typeResult; RefPtr<Expr> TranslateTypeNodeImpl(const RefPtr<Expr> & node); RefPtr<Type> ExtractTypeFromTypeRepr(const RefPtr<Expr>& typeRepr); @@ -367,19 +380,38 @@ namespace Slang DeclRef<GenericDecl> genericDeclRef, List<RefPtr<Expr>> const& args); - // This routine is a bottleneck for all declaration checking, + // These routines are bottlenecks for semantic checking, // so that we can add some quality-of-life features for users // in cases where the compiler crashes - void dispatchDecl(DeclBase* decl); - void dispatchStmt(Stmt* stmt); + // + void dispatchStmt(Stmt* stmt, FuncDecl* parentFunc, OuterStmtInfo* outerStmts); void dispatchExpr(Expr* expr); - // 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> decl, DeclCheckState state); + /// Ensure that a declaration has been checked up to some state + /// (aka, a phase of semantic checking) so that we can safely + /// perform certain operations on it. + /// + /// Calling `ensureDecl` may cause the type-checker to recursively + /// start checking `decl` on top of the stack that is already + /// doing other semantic checking. Care should be taken when relying + /// on this function to avoid blowing out the stack or (even worse + /// creating a circular dependency). + /// + void ensureDecl(Decl* decl, DeclCheckState state); + + /// Helper routine allowing `ensureDecl` to be called on a `DeclRef` + void ensureDecl(DeclRefBase const& declRef, DeclCheckState state) + { + ensureDecl(declRef.getDecl(), state); + } - void EnusreAllDeclsRec(RefPtr<Decl> decl); + /// Helper routine allowing `ensureDecl` to be used on a `DeclBase` + /// + /// `DeclBase` is the base clas of `Decl` and `DeclGroup`. When + /// called on a `DeclGroup` this function just calls `ensureDecl()` + /// on each declaration in the group. + /// + void ensureDeclBase(DeclBase* decl, DeclCheckState state); // 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 @@ -597,23 +629,11 @@ namespace Slang RefPtr<Type> toType, RefPtr<Expr> fromExpr); - void CheckVarDeclCommon(RefPtr<VarDeclBase> varDecl); - // Fill in default substitutions for the 'subtype' part of a type constraint decl void CheckConstraintSubType(TypeExp& typeExp); - void CheckGenericConstraintDecl(GenericTypeConstraintDecl* decl); - - void checkDecl(Decl* decl); - void checkGenericDeclHeader(GenericDecl* genericDecl); - void visitGenericDecl(GenericDecl* genericDecl); - - void visitGenericTypeConstraintDecl(GenericTypeConstraintDecl * genericConstraintDecl); - - void visitInheritanceDecl(InheritanceDecl* inheritanceDecl); - RefPtr<ConstantIntVal> checkConstantIntVal( RefPtr<Expr> expr); @@ -626,14 +646,6 @@ namespace Slang RefPtr<Expr> expr, String* outVal); - void visitSyntaxDecl(SyntaxDecl*); - - void visitAttributeDecl(AttributeDecl*); - - void visitGenericTypeParamDecl(GenericTypeParamDecl*); - - void visitGenericValueParamDecl(GenericValueParamDecl*); - void visitModifier(Modifier*); AttributeDecl* lookUpAttributeDecl(Name* attributeName, Scope* scope); @@ -655,11 +667,6 @@ namespace Slang void checkModifiers(ModifiableSyntaxNode* syntaxNode); - /// Perform checking of interface conformaces for this decl and all its children - void checkInterfaceConformancesRec(Decl* decl); - - void visitModuleDecl(ModuleDecl* programNode); - bool doesSignatureMatchRequirement( DeclRef<CallableDecl> satisfyingMemberDeclRef, DeclRef<CallableDecl> requiredMemberDeclRef, @@ -744,29 +751,13 @@ namespace Slang void checkAggTypeConformance(AggTypeDecl* decl); - void visitAggTypeDecl(AggTypeDecl* decl); - bool isIntegerBaseType(BaseType baseType); // Validate that `type` is a suitable type to use // as the tag type for an `enum` void validateEnumTagType(Type* type, SourceLoc const& loc); - void visitEnumDecl(EnumDecl* decl); - - void visitEnumCaseDecl(EnumCaseDecl* decl); - - void visitDeclGroup(DeclGroup* declGroup); - - void visitTypeDefDecl(TypeDefDecl* decl); - - void visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl); - - void visitAssocTypeDecl(AssocTypeDecl* decl); - - void checkStmt(Stmt* stmt); - - void visitFuncDecl(FuncDecl* functionNode); + void checkStmt(Stmt* stmt, FuncDecl* outerFunction, OuterStmtInfo* outerStmts); void getGenericParams( GenericDecl* decl, @@ -788,53 +779,9 @@ namespace Slang void ValidateFunctionRedeclaration(FuncDecl* funcDecl); - void visitScopeDecl(ScopeDecl*); - - void visitParamDecl(ParamDecl* paramDecl); - - void VisitFunctionDeclaration(FuncDecl *functionNode); - - void visitDeclStmt(DeclStmt* stmt); - - void visitBlockStmt(BlockStmt* stmt); - - void visitSeqStmt(SeqStmt* stmt); - - template<typename T> - T* FindOuterStmt(); - - void visitBreakStmt(BreakStmt *stmt); - - void visitContinueStmt(ContinueStmt *stmt); - - void PushOuterStmt(Stmt* stmt); - - void PopOuterStmt(Stmt* /*stmt*/); - RefPtr<Expr> checkPredicateExpr(Expr* expr); - void visitDoWhileStmt(DoWhileStmt *stmt); - - void visitForStmt(ForStmt *stmt); - RefPtr<Expr> checkExpressionAndExpectIntegerConstant(RefPtr<Expr> expr, RefPtr<IntVal>* outIntVal); - void visitCompileTimeForStmt(CompileTimeForStmt* stmt); - - void visitSwitchStmt(SwitchStmt* stmt); - - void visitCaseStmt(CaseStmt* stmt); - - void visitDefaultStmt(DefaultStmt* stmt); - - void visitIfStmt(IfStmt *stmt); - - void visitUnparsedStmt(UnparsedStmt*); - - void visitEmptyStmt(EmptyStmt*); - - void visitDiscardStmt(DiscardStmt*); - - void visitReturnStmt(ReturnStmt *stmt); IntegerLiteralValue GetMinBound(RefPtr<IntVal> val); @@ -842,22 +789,8 @@ namespace Slang void validateArraySizeForVariable(VarDeclBase* varDecl); - void visitVarDecl(VarDecl* varDecl); - - void visitWhileStmt(WhileStmt *stmt); - - void visitExpressionStmt(ExpressionStmt *stmt); - - RefPtr<Expr> visitBoolLiteralExpr(BoolLiteralExpr* expr); - RefPtr<Expr> visitIntegerLiteralExpr(IntegerLiteralExpr* expr); - RefPtr<Expr> visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr); - RefPtr<Expr> visitStringLiteralExpr(StringLiteralExpr* expr); - IntVal* GetIntVal(IntegerLiteralExpr* expr); - Linkage* getLinkage() { return m_linkage; } - NamePool* getNamePool() { return getLinkage()->getNamePool(); } - Name* getName(String const& text) { return getNamePool()->getName(text); @@ -897,33 +830,18 @@ namespace Slang RefPtr<Type> elementType, RefPtr<IntVal> elementCount); - RefPtr<Expr> visitIndexExpr(IndexExpr* subscriptExpr); - - RefPtr<Expr> visitParenExpr(ParenExpr* expr); - // /// Given an immutable `expr` used as an l-value emit a special diagnostic if it was derived from `this`. void maybeDiagnoseThisNotLValue(Expr* expr); - RefPtr<Expr> visitAssignExpr(AssignExpr* expr); - void registerExtension(ExtensionDecl* decl); - void visitExtensionDecl(ExtensionDecl* decl); - // 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<Type> findResultTypeForConstructorDecl(ConstructorDecl* decl); - void visitConstructorDecl(ConstructorDecl* decl); - - - void visitSubscriptDecl(SubscriptDecl* decl); - - void visitAccessorDecl(AccessorDecl* decl); - // @@ -1308,28 +1226,83 @@ namespace Slang RefPtr<Expr> baseExpr, OverloadResolveContext& context); - RefPtr<Expr> visitGenericAppExpr(GenericAppExpr* genericAppExpr); - /// Check a generic application where the operands have already been checked. RefPtr<Expr> checkGenericAppWithCheckedArgs(GenericAppExpr* genericAppExpr); - RefPtr<Expr> visitSharedTypeExpr(SharedTypeExpr* expr); - - RefPtr<Expr> visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr); - RefPtr<Expr> CheckExpr(RefPtr<Expr> expr); RefPtr<Expr> CheckInvokeExprWithCheckedOperands(InvokeExpr *expr); + // Get the type to use when referencing a declaration + QualType GetTypeForDeclRef(DeclRef<Decl> declRef); + + // + // + // + + RefPtr<Expr> MaybeDereference(RefPtr<Expr> inExpr); + + RefPtr<Expr> CheckSwizzleExpr( + MemberExpr* memberRefExpr, + RefPtr<Type> baseElementType, + IntegerLiteralValue baseElementCount); + + RefPtr<Expr> CheckSwizzleExpr( + MemberExpr* memberRefExpr, + RefPtr<Type> baseElementType, + RefPtr<IntVal> baseElementCount); + + // Look up a static member + // @param expr Can be StaticMemberExpr or MemberExpr + // @param baseExpression Is the underlying type expression determined from resolving expr + RefPtr<Expr> _lookupStaticMember(RefPtr<DeclRefExpr> expr, RefPtr<Expr> baseExpression); + + RefPtr<Expr> visitStaticMemberExpr(StaticMemberExpr* expr); + + RefPtr<Expr> lookupMemberResultFailure( + DeclRefExpr* expr, + QualType const& baseType); + + SharedSemanticsContext & operator = (const SharedSemanticsContext &) = delete; + + + // + + void importModuleIntoScope(Scope* scope, ModuleDecl* moduleDecl); + }; + + struct SemanticsExprVisitor + : public SemanticsVisitor + , ExprVisitor<SemanticsExprVisitor, RefPtr<Expr>> + { + public: + SemanticsExprVisitor(SharedSemanticsContext* shared) + : SemanticsVisitor(shared) + {} + + RefPtr<Expr> visitBoolLiteralExpr(BoolLiteralExpr* expr); + RefPtr<Expr> visitIntegerLiteralExpr(IntegerLiteralExpr* expr); + RefPtr<Expr> visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr); + RefPtr<Expr> visitStringLiteralExpr(StringLiteralExpr* expr); + + RefPtr<Expr> visitIndexExpr(IndexExpr* subscriptExpr); + + RefPtr<Expr> visitParenExpr(ParenExpr* expr); + + RefPtr<Expr> visitAssignExpr(AssignExpr* expr); + + RefPtr<Expr> visitGenericAppExpr(GenericAppExpr* genericAppExpr); + + RefPtr<Expr> visitSharedTypeExpr(SharedTypeExpr* expr); + + RefPtr<Expr> visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr); + RefPtr<Expr> visitInvokeExpr(InvokeExpr *expr); RefPtr<Expr> visitVarExpr(VarExpr *expr); RefPtr<Expr> visitTypeCastExpr(TypeCastExpr * expr); - // Get the type to use when referencing a declaration - QualType GetTypeForDeclRef(DeclRef<Decl> declRef); - // // Some syntax nodes should not occur in the concrete input syntax, // and will only appear *after* checking is complete. We need to @@ -1355,50 +1328,88 @@ namespace Slang #undef CASE - // - // - // + RefPtr<Expr> visitStaticMemberExpr(StaticMemberExpr* expr); - RefPtr<Expr> MaybeDereference(RefPtr<Expr> inExpr); + RefPtr<Expr> visitMemberExpr(MemberExpr * expr); - RefPtr<Expr> CheckSwizzleExpr( - MemberExpr* memberRefExpr, - RefPtr<Type> baseElementType, - IntegerLiteralValue baseElementCount); + RefPtr<Expr> visitInitializerListExpr(InitializerListExpr* expr); - RefPtr<Expr> CheckSwizzleExpr( - MemberExpr* memberRefExpr, - RefPtr<Type> baseElementType, - RefPtr<IntVal> baseElementCount); + RefPtr<Expr> visitThisExpr(ThisExpr* expr); + }; - // Look up a static member - // @param expr Can be StaticMemberExpr or MemberExpr - // @param baseExpression Is the underlying type expression determined from resolving expr - RefPtr<Expr> _lookupStaticMember(RefPtr<DeclRefExpr> expr, RefPtr<Expr> baseExpression); + struct SemanticsStmtVisitor + : public SemanticsVisitor + , StmtVisitor<SemanticsStmtVisitor> + { + SemanticsStmtVisitor(SharedSemanticsContext* shared, FuncDecl* parentFunc, OuterStmtInfo* outerStmts) + : SemanticsVisitor(shared) + , m_parentFunc(parentFunc) + , m_outerStmts(outerStmts) + {} - RefPtr<Expr> visitStaticMemberExpr(StaticMemberExpr* expr); + /// The parent function (if any) that surrounds the statement being checked. + // TODO: This should probably be a more general case like `CallableDecl` + FuncDecl* m_parentFunc = nullptr; - RefPtr<Expr> lookupMemberResultFailure( - DeclRefExpr* expr, - QualType const& baseType); + /// The linked list of lexically surrounding statements. + OuterStmtInfo* m_outerStmts = nullptr; - RefPtr<Expr> visitMemberExpr(MemberExpr * expr); + FuncDecl* getParentFunc() { return m_parentFunc; } - SemanticsVisitor & operator = (const SemanticsVisitor &) = delete; + void checkStmt(Stmt* stmt); + template<typename T> + T* FindOuterStmt(); - // + void visitDeclStmt(DeclStmt* stmt); - RefPtr<Expr> visitInitializerListExpr(InitializerListExpr* expr); + void visitBlockStmt(BlockStmt* stmt); - void importModuleIntoScope(Scope* scope, ModuleDecl* moduleDecl); + void visitSeqStmt(SeqStmt* stmt); - void visitEmptyDecl(EmptyDecl* /*decl*/); + void visitBreakStmt(BreakStmt *stmt); - void visitImportDecl(ImportDecl* decl); + void visitContinueStmt(ContinueStmt *stmt); - // Perform semantic checking of an object-oriented `this` - // expression. - RefPtr<Expr> visitThisExpr(ThisExpr* expr); + void visitDoWhileStmt(DoWhileStmt *stmt); + + void visitForStmt(ForStmt *stmt); + + void visitCompileTimeForStmt(CompileTimeForStmt* stmt); + + void visitSwitchStmt(SwitchStmt* stmt); + + void visitCaseStmt(CaseStmt* stmt); + + void visitDefaultStmt(DefaultStmt* stmt); + + void visitIfStmt(IfStmt *stmt); + + void visitUnparsedStmt(UnparsedStmt*); + + void visitEmptyStmt(EmptyStmt*); + + void visitDiscardStmt(DiscardStmt*); + + void visitReturnStmt(ReturnStmt *stmt); + + void visitWhileStmt(WhileStmt *stmt); + + void visitExpressionStmt(ExpressionStmt *stmt); + }; + + struct SemanticsDeclVisitorBase + : public SemanticsVisitor + { + SemanticsDeclVisitorBase(SharedSemanticsContext* shared) + : SemanticsVisitor(shared) + {} + + void checkBodyStmt(Stmt* stmt, FuncDecl* parentDecl) + { + checkStmt(stmt, parentDecl, nullptr); + } + + void checkModule(ModuleDecl* programNode); }; } diff --git a/source/slang/slang-check-modifier.cpp b/source/slang/slang-check-modifier.cpp index 22db4375a..66be88a1e 100644 --- a/source/slang/slang-check-modifier.cpp +++ b/source/slang/slang-check-modifier.cpp @@ -153,7 +153,10 @@ namespace Slang structAttribDef->ParentDecl->Members.add(attribDecl.Ptr()); structAttribDef->ParentDecl->memberDictionaryIsValid = false; // do necessary checks on this newly constructed node - checkDecl(attribDecl.Ptr()); + + // TODO: what check state is relevant here? + ensureDecl(attribDecl, DeclCheckState::Checked); + return attribDecl.Ptr(); } @@ -368,11 +371,14 @@ namespace Slang } else if (auto userDefAttr = as<UserDefinedAttribute>(attr)) { + // check arguments against attribute parameters defined in attribClassDecl Index paramIndex = 0; auto params = attribClassDecl->getMembersOfType<ParamDecl>(); for (auto paramDecl : params) { + ensureDecl(paramDecl, DeclCheckState::CanUseTypeOfValueDecl); + if (paramIndex < attr->args.getCount()) { auto & arg = attr->args[paramIndex]; diff --git a/source/slang/slang-check-overload.cpp b/source/slang/slang-check-overload.cpp index dbe6c3aab..a7ae187a7 100644 --- a/source/slang/slang-check-overload.cpp +++ b/source/slang/slang-check-overload.cpp @@ -600,7 +600,7 @@ namespace Slang OverloadResolveContext& context) { auto funcDecl = funcDeclRef.getDecl(); - checkDecl(funcDecl); + ensureDecl(funcDecl, DeclCheckState::CanUseFuncSignature); // If this function is a redeclaration, // then we don't want to include it multiple times, @@ -659,7 +659,7 @@ namespace Slang OverloadResolveContext& context, RefPtr<Type> resultType) { - checkDecl(ctorDeclRef.getDecl()); + ensureDecl(ctorDeclRef, DeclCheckState::CanUseFuncSignature); // `typeItem` refers to the type being constructed (the thing // that was applied as a function) so we need to construct @@ -684,7 +684,7 @@ namespace Slang DeclRef<GenericDecl> genericDeclRef, OverloadResolveContext& context) { - checkDecl(genericDeclRef.getDecl()); + ensureDecl(genericDeclRef, DeclCheckState::CanSpecializeGeneric); ConstraintSystem constraints; constraints.loc = context.loc; @@ -1341,7 +1341,7 @@ namespace Slang { if (auto genericDeclRef = baseItem.declRef.as<GenericDecl>()) { - checkDecl(genericDeclRef.getDecl()); + ensureDecl(genericDeclRef, DeclCheckState::CanSpecializeGeneric); OverloadCandidate candidate; candidate.flavor = OverloadCandidate::Flavor::Generic; @@ -1376,7 +1376,7 @@ namespace Slang } } - RefPtr<Expr> SemanticsVisitor::visitGenericAppExpr(GenericAppExpr* genericAppExpr) + RefPtr<Expr> SemanticsExprVisitor::visitGenericAppExpr(GenericAppExpr* genericAppExpr) { // Start by checking the base expression and arguments. auto& baseExpr = genericAppExpr->FunctionExpr; diff --git a/source/slang/slang-check-shader.cpp b/source/slang/slang-check-shader.cpp index 2ac449e83..333690b36 100644 --- a/source/slang/slang-check-shader.cpp +++ b/source/slang/slang-check-shader.cpp @@ -1415,7 +1415,8 @@ static bool doesParameterMatch( { SLANG_ASSERT(argCount == getSpecializationParamCount()); - SemanticsVisitor visitor(getLinkage(), sink); + SharedSemanticsContext semanticsContext(getLinkage(), sink); + SemanticsVisitor visitor(&semanticsContext); RefPtr<Module::ModuleSpecializationInfo> specializationInfo = new Module::ModuleSpecializationInfo(); @@ -1588,7 +1589,8 @@ static bool doesParameterMatch( auto args = inArgs; auto argCount = inArgCount; - SemanticsVisitor visitor(getLinkage(), sink); + SharedSemanticsContext sharedSemanticsContext(getLinkage(), sink); + SemanticsVisitor visitor(&sharedSemanticsContext); // The first N arguments will be for the explicit generic parameters // of the entry point (if it has any). @@ -1737,9 +1739,11 @@ static bool doesParameterMatch( // auto linkage = endToEndReq->getLinkage(); auto sink = endToEndReq->getSink(); - SemanticsVisitor semantics( + + SharedSemanticsContext sharedSemanticsContext( linkage, sink); + SemanticsVisitor semantics(&sharedSemanticsContext); // We will be looping over the generic argument strings // that the user provided via the API (or command line), @@ -1782,7 +1786,8 @@ static bool doesParameterMatch( // TODO: We should cache and re-use specialized types // when the exact same arguments are provided again later. - SemanticsVisitor visitor(this, sink); + SharedSemanticsContext sharedSemanticsContext(this, sink); + SemanticsVisitor visitor(&sharedSemanticsContext); SpecializationParams specializationParams; _collectExistentialSpecializationParamsRec(specializationParams, unspecializedType); @@ -1839,7 +1844,7 @@ static bool doesParameterMatch( // We have an appropriate number of arguments for the global specialization parameters, // and now we need to check that the arguments conform to the declared constraints. // - SemanticsVisitor visitor(linkage, sink); + SharedSemanticsContext visitor(linkage, sink); List<SpecializationArg> specializationArgs; _extractSpecializationArgs(unspecializedProgram, specializationArgExprs, specializationArgs, sink); diff --git a/source/slang/slang-check-stmt.cpp b/source/slang/slang-check-stmt.cpp index 5e9676a23..7a157a1fd 100644 --- a/source/slang/slang-check-stmt.cpp +++ b/source/slang/slang-check-stmt.cpp @@ -5,34 +5,64 @@ namespace Slang { - void SemanticsVisitor::checkStmt(Stmt* stmt) + namespace + { + /// RAII-like type for establishing an "outer" statement during nested checks. + /// + /// The `SemanticsStmtVisitor` maintains a linked list of outer statements + /// using `OuterStmtInfo` records stored on the recursive call stack during + /// checking. This type creates a sub-`SemanticsStmtVisitor` that has one + /// additional outer statement added to the stack of outer statements. + /// + /// The outer statements are used to validate and resolve things like + /// the target of `break` or `continue` statements. + /// + struct WithOuterStmt : public SemanticsStmtVisitor + { + public: + WithOuterStmt(SemanticsStmtVisitor* visitor, Stmt* outerStmt) + : SemanticsStmtVisitor(*visitor) + { + m_parentFunc = visitor->m_parentFunc; + + m_outerStmt.next = visitor->m_outerStmts; + m_outerStmt.stmt = outerStmt; + m_outerStmts = &m_outerStmt; + } + + private: + OuterStmtInfo m_outerStmt; + }; + } + + void SemanticsVisitor::checkStmt(Stmt* stmt, FuncDecl* parentDecl, OuterStmtInfo* outerStmts) { if (!stmt) return; - dispatchStmt(stmt); + dispatchStmt(stmt, parentDecl, outerStmts); checkModifiers(stmt); } - void SemanticsVisitor::visitDeclStmt(DeclStmt* stmt) + void SemanticsStmtVisitor::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 + // When we encounter a declaration during statement checking, + // we expect that it hasn't been checked yet (because otherwise + // it would be referenced before its declaration point), but + // we will bottleneck through the `ensureDecl()` path anyway, + // to unify with the rest of semantic checking. // - // 2. `EnsureDecl()` is specialized for `Decl*` instead of `DeclBase*` - // and trying to special case `DeclGroup*` here feels silly. + // TODO: This logic might not suffice for something like a + // local `struct` declaration, where it would have members + // that need to be recursively checked. // - dispatchDecl(stmt->decl); - checkModifiers(stmt->decl); + ensureDeclBase(stmt->decl, DeclCheckState::Checked); } - void SemanticsVisitor::visitBlockStmt(BlockStmt* stmt) + void SemanticsStmtVisitor::visitBlockStmt(BlockStmt* stmt) { checkStmt(stmt->body); } - void SemanticsVisitor::visitSeqStmt(SeqStmt* stmt) + void SemanticsStmtVisitor::visitSeqStmt(SeqStmt* stmt) { for(auto ss : stmt->stmts) { @@ -40,13 +70,17 @@ namespace Slang } } + void SemanticsStmtVisitor::checkStmt(Stmt* stmt) + { + SemanticsVisitor::checkStmt(stmt, m_parentFunc, m_outerStmts); + } + template<typename T> - T* SemanticsVisitor::FindOuterStmt() + T* SemanticsStmtVisitor::FindOuterStmt() { - const Index outerStmtCount = outerStmts.getCount(); - for (Index ii = outerStmtCount; ii > 0; --ii) + for(auto outerStmtInfo = m_outerStmts; outerStmtInfo; outerStmtInfo = outerStmtInfo->next) { - auto outerStmt = outerStmts[ii-1]; + auto outerStmt = outerStmtInfo->stmt; auto found = as<T>(outerStmt); if (found) return found; @@ -54,7 +88,7 @@ namespace Slang return nullptr; } - void SemanticsVisitor::visitBreakStmt(BreakStmt *stmt) + void SemanticsStmtVisitor::visitBreakStmt(BreakStmt *stmt) { auto outer = FindOuterStmt<BreakableStmt>(); if (!outer) @@ -64,7 +98,7 @@ namespace Slang stmt->parentStmt = outer; } - void SemanticsVisitor::visitContinueStmt(ContinueStmt *stmt) + void SemanticsStmtVisitor::visitContinueStmt(ContinueStmt *stmt) { auto outer = FindOuterStmt<LoopStmt>(); if (!outer) @@ -74,16 +108,6 @@ namespace Slang stmt->parentStmt = outer; } - void SemanticsVisitor::PushOuterStmt(Stmt* stmt) - { - outerStmts.add(stmt); - } - - void SemanticsVisitor::PopOuterStmt(Stmt* /*stmt*/) - { - outerStmts.removeAt(outerStmts.getCount() - 1); - } - RefPtr<Expr> SemanticsVisitor::checkPredicateExpr(Expr* expr) { RefPtr<Expr> e = expr; @@ -92,18 +116,18 @@ namespace Slang return e; } - void SemanticsVisitor::visitDoWhileStmt(DoWhileStmt *stmt) + void SemanticsStmtVisitor::visitDoWhileStmt(DoWhileStmt *stmt) { - PushOuterStmt(stmt); - stmt->Predicate = checkPredicateExpr(stmt->Predicate); - checkStmt(stmt->Statement); + WithOuterStmt subContext(this, stmt); - PopOuterStmt(stmt); + stmt->Predicate = checkPredicateExpr(stmt->Predicate); + subContext.checkStmt(stmt->Statement); } - void SemanticsVisitor::visitForStmt(ForStmt *stmt) + void SemanticsStmtVisitor::visitForStmt(ForStmt *stmt) { - PushOuterStmt(stmt); + WithOuterStmt subContext(this, stmt); + checkStmt(stmt->InitialStatement); if (stmt->PredicateExpression) { @@ -113,9 +137,7 @@ namespace Slang { stmt->SideEffectExpression = CheckExpr(stmt->SideEffectExpression); } - checkStmt(stmt->Statement); - - PopOuterStmt(stmt); + subContext.checkStmt(stmt->Statement); } RefPtr<Expr> SemanticsVisitor::checkExpressionAndExpectIntegerConstant(RefPtr<Expr> expr, RefPtr<IntVal>* outIntVal) @@ -127,9 +149,9 @@ namespace Slang return expr; } - void SemanticsVisitor::visitCompileTimeForStmt(CompileTimeForStmt* stmt) + void SemanticsStmtVisitor::visitCompileTimeForStmt(CompileTimeForStmt* stmt) { - PushOuterStmt(stmt); + WithOuterStmt subContext(this, stmt); stmt->varDecl->type.type = getSession()->getIntType(); addModifier(stmt->varDecl, new ConstModifier()); @@ -154,27 +176,23 @@ namespace Slang stmt->rangeBeginVal = rangeBeginVal; stmt->rangeEndVal = rangeEndVal; - checkStmt(stmt->body); - - - PopOuterStmt(stmt); + subContext.checkStmt(stmt->body); } - void SemanticsVisitor::visitSwitchStmt(SwitchStmt* stmt) + void SemanticsStmtVisitor::visitSwitchStmt(SwitchStmt* stmt) { - PushOuterStmt(stmt); + WithOuterStmt subContext(this, stmt); + // TODO(tfoley): need to coerce condition to an integral type... stmt->condition = CheckExpr(stmt->condition); - checkStmt(stmt->body); + subContext.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 SemanticsVisitor::visitCaseStmt(CaseStmt* stmt) + void SemanticsStmtVisitor::visitCaseStmt(CaseStmt* stmt) { // TODO(tfoley): Need to coerce to type being switch on, // and ensure that value is a compile-time constant @@ -195,7 +213,7 @@ namespace Slang stmt->parentStmt = switchStmt; } - void SemanticsVisitor::visitDefaultStmt(DefaultStmt* stmt) + void SemanticsStmtVisitor::visitDefaultStmt(DefaultStmt* stmt) { auto switchStmt = FindOuterStmt<SwitchStmt>(); if (!switchStmt) @@ -205,30 +223,31 @@ namespace Slang stmt->parentStmt = switchStmt; } - void SemanticsVisitor::visitIfStmt(IfStmt *stmt) + void SemanticsStmtVisitor::visitIfStmt(IfStmt *stmt) { stmt->Predicate = checkPredicateExpr(stmt->Predicate); checkStmt(stmt->PositiveStatement); checkStmt(stmt->NegativeStatement); } - void SemanticsVisitor::visitUnparsedStmt(UnparsedStmt*) + void SemanticsStmtVisitor::visitUnparsedStmt(UnparsedStmt*) { // Nothing to do } - void SemanticsVisitor::visitEmptyStmt(EmptyStmt*) + void SemanticsStmtVisitor::visitEmptyStmt(EmptyStmt*) { // Nothing to do } - void SemanticsVisitor::visitDiscardStmt(DiscardStmt*) + void SemanticsStmtVisitor::visitDiscardStmt(DiscardStmt*) { // Nothing to do } - void SemanticsVisitor::visitReturnStmt(ReturnStmt *stmt) + void SemanticsStmtVisitor::visitReturnStmt(ReturnStmt *stmt) { + auto function = getParentFunc(); if (!stmt->Expression) { if (function && !function->ReturnType.Equals(getSession()->getVoidType())) @@ -257,15 +276,14 @@ namespace Slang } } - void SemanticsVisitor::visitWhileStmt(WhileStmt *stmt) + void SemanticsStmtVisitor::visitWhileStmt(WhileStmt *stmt) { - PushOuterStmt(stmt); + WithOuterStmt subContext(this, stmt); stmt->Predicate = checkPredicateExpr(stmt->Predicate); - checkStmt(stmt->Statement); - PopOuterStmt(stmt); + subContext.checkStmt(stmt->Statement); } - void SemanticsVisitor::visitExpressionStmt(ExpressionStmt *stmt) + void SemanticsStmtVisitor::visitExpressionStmt(ExpressionStmt *stmt) { stmt->Expression = CheckExpr(stmt->Expression); } diff --git a/source/slang/slang-check-type.cpp b/source/slang/slang-check-type.cpp index 0786d3166..ea808e456 100644 --- a/source/slang/slang-check-type.cpp +++ b/source/slang/slang-check-type.cpp @@ -11,9 +11,12 @@ namespace Slang TypeExp typeExp, DiagnosticSink* sink) { - SemanticsVisitor visitor( + SharedSemanticsContext sharedSemanticsContext( linkage, sink); + SemanticsVisitor visitor(&sharedSemanticsContext); + + auto typeOut = visitor.CheckProperType(typeExp); return typeOut.type; } @@ -185,7 +188,7 @@ namespace Slang // auto genericDeclRef = genericDeclRefType->GetDeclRef(); - checkDecl(genericDeclRef.decl); + ensureDecl(genericDeclRef, DeclCheckState::CanSpecializeGeneric); List<RefPtr<Expr>> args; for (RefPtr<Decl> member : genericDeclRef.getDecl()->Members) { @@ -331,7 +334,7 @@ namespace Slang declRef).as<VectorExpressionType>(); } - RefPtr<Expr> SemanticsVisitor::visitSharedTypeExpr(SharedTypeExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitSharedTypeExpr(SharedTypeExpr* expr) { if (!expr->type.Ptr()) { @@ -341,7 +344,7 @@ namespace Slang return expr; } - RefPtr<Expr> SemanticsVisitor::visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr) + RefPtr<Expr> SemanticsExprVisitor::visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr) { // We have an expression of the form `__TaggedUnion(A, B, ...)` // which will evaluate to a tagged-union type over `A`, `B`, etc. diff --git a/source/slang/slang-check.cpp b/source/slang/slang-check.cpp index 2d8d916a5..704d37d01 100644 --- a/source/slang/slang-check.cpp +++ b/source/slang/slang-check.cpp @@ -156,39 +156,27 @@ namespace Slang void checkTranslationUnit( TranslationUnitRequest* translationUnit) { - SemanticsVisitor visitor( + SharedSemanticsContext sharedSemanticsContext( translationUnit->compileRequest->getLinkage(), translationUnit->compileRequest->getSink()); + SemanticsDeclVisitorBase visitor(&sharedSemanticsContext); + // Apply the visitor to do the main semantic // checking that is required on all declarations // in the translation unit. - visitor.checkDecl(translationUnit->getModuleDecl()); - translationUnit->getModule()->_collectShaderParams(); - } + visitor.checkModule(translationUnit->getModuleDecl()); - void SemanticsVisitor::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(...) - { - getSink()->noteInternalErrorLoc(decl->loc); - throw; - } + translationUnit->getModule()->_collectShaderParams(); } - void SemanticsVisitor::dispatchStmt(Stmt* stmt) + void SemanticsVisitor::dispatchStmt(Stmt* stmt, FuncDecl* parentFunc, OuterStmtInfo* outerStmts) { + SemanticsStmtVisitor visitor(getShared(), parentFunc, outerStmts); try { - StmtVisitor::dispatch(stmt); + visitor.dispatch(stmt); } catch(AbortCompilationException&) { throw; } catch(...) @@ -200,9 +188,10 @@ namespace Slang void SemanticsVisitor::dispatchExpr(Expr* expr) { + SemanticsExprVisitor visitor(getShared()); try { - ExprVisitor::dispatch(expr); + visitor.dispatch(expr); } catch(AbortCompilationException&) { throw; } catch(...) diff --git a/source/slang/slang-check.h b/source/slang/slang-check.h index 0d6342ca7..94deba1aa 100644 --- a/source/slang/slang-check.h +++ b/source/slang/slang-check.h @@ -35,4 +35,5 @@ namespace Slang DiagnosticSink* sink); bool isGlobalShaderParameter(VarDeclBase* decl); + bool isFromStdLib(Decl* decl); } diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp index 0a77a259a..f46a15d02 100644 --- a/source/slang/slang-lookup.cpp +++ b/source/slang/slang-lookup.cpp @@ -4,12 +4,12 @@ namespace Slang { -void checkDecl(SemanticsVisitor* visitor, Decl* decl); +void ensureDecl(SemanticsVisitor* visitor, Decl* decl, DeclCheckState state); // DeclRef<ExtensionDecl> ApplyExtensionToType( - SemanticsVisitor* semantics, + SemanticsVisitor* semantics, ExtensionDecl* extDecl, RefPtr<Type> type); @@ -241,6 +241,11 @@ DeclRef<Decl> maybeSpecializeInterfaceDeclRef( { if (auto superInterfaceDeclRef = superTypeDeclRef.as<InterfaceDecl>()) { + // TODO: This case should probably loop back into the semantic + // checking logic (when available) in order to ensure that + // appropriate witness values have been registered for (at least) + // the associated type requirements of the super-type. + // Create a subtype witness value to note the subtype relationship // that makes this specialization valid. // @@ -358,6 +363,11 @@ void DoLocalLookupImpl( // Consider lookup via extension if( auto aggTypeDeclRef = containerDeclRef.as<AggTypeDecl>() ) { + if (request.semantics) + { + ensureDecl(request.semantics, containerDeclRef.getDecl(), DeclCheckState::ReadyForLookup); + } + RefPtr<Type> type = DeclRefType::Create( session, aggTypeDeclRef); @@ -397,6 +407,8 @@ void DoLocalLookupImpl( RefPtr<DeclRefType> targetDeclRefType; if (auto extDeclRef = containerDeclRef.as<ExtensionDecl>()) { + ensureDecl(request.semantics, extDeclRef.getDecl(), DeclCheckState::CanUseExtensionTargetType); + targetDeclRefType = as<DeclRefType>(extDeclRef.getDecl()->targetType); SLANG_ASSERT(targetDeclRefType); int diff = 0; @@ -414,7 +426,7 @@ void DoLocalLookupImpl( auto baseInterfaces = getMembersOfType<InheritanceDecl>(containerDeclRef); for (auto inheritanceDeclRef : baseInterfaces) { - checkDecl(request.semantics, inheritanceDeclRef.decl); + ensureDecl(request.semantics, inheritanceDeclRef.getDecl(), DeclCheckState::CanUseBaseOfInheritanceDecl); auto baseType = inheritanceDeclRef.getDecl()->base.type.dynamicCast<DeclRefType>(); SLANG_ASSERT(baseType); @@ -556,7 +568,7 @@ LookupResult lookUpLocal( SemanticsVisitor* semantics, Name* name, DeclRef<ContainerDecl> containerDeclRef, - LookupMask mask) + LookupMask mask) { LookupRequest request; request.semantics = semantics; diff --git a/source/slang/slang-lookup.h b/source/slang/slang-lookup.h index 705b952f3..76a097d8c 100644 --- a/source/slang/slang-lookup.h +++ b/source/slang/slang-lookup.h @@ -31,7 +31,7 @@ LookupResult lookUpLocal( SemanticsVisitor* semantics, Name* name, DeclRef<ContainerDecl> containerDeclRef, - LookupMask mask = LookupMask::Default); + LookupMask mask = LookupMask::Default); // Perform member lookup in the context of a type LookupResult lookUpMember( diff --git a/source/slang/slang-syntax-base-defs.h b/source/slang/slang-syntax-base-defs.h index 2f7c8b1fa..afec117ca 100644 --- a/source/slang/slang-syntax-base-defs.h +++ b/source/slang/slang-syntax-base-defs.h @@ -275,7 +275,7 @@ ABSTRACT_SYNTAX_CLASS(Decl, DeclBase) ) - FIELD_INIT(DeclCheckState, checkState, DeclCheckState::Unchecked) + FIELD_INIT(DeclCheckStateExt, checkState, DeclCheckState::Unchecked) // The next declaration defined in the same container with the same name DECL_FIELD(Decl*, nextInContainerWithSameName RAW(= nullptr)) @@ -284,8 +284,8 @@ ABSTRACT_SYNTAX_CLASS(Decl, DeclBase) bool IsChecked(DeclCheckState state) { return checkState >= state; } void SetCheckState(DeclCheckState state) { - SLANG_RELEASE_ASSERT(state >= checkState); - checkState = state; + SLANG_RELEASE_ASSERT(state >= checkState.getState()); + checkState.setState(state); } ) END_SYNTAX_CLASS() diff --git a/source/slang/slang-syntax.cpp b/source/slang/slang-syntax.cpp index c4152d78c..e41e5517c 100644 --- a/source/slang/slang-syntax.cpp +++ b/source/slang/slang-syntax.cpp @@ -2021,7 +2021,15 @@ void Type::accept(IValVisitor* visitor, void* extra) RefPtr<Decl> decl, RefPtr<MagicTypeModifier> modifier) { - session->magicDecls[modifier->name] = decl.Ptr(); + // In some cases the modifier will have been applied to the + // "inner" declaration of a `GenericDecl`, but what we + // actually want to register is the generic itself. + // + auto declToRegister = decl; + if(auto genericDecl = as<GenericDecl>(decl->ParentDecl)) + declToRegister = genericDecl; + + session->magicDecls[modifier->name] = declToRegister.Ptr(); } RefPtr<Decl> findMagicDecl( diff --git a/source/slang/slang-syntax.h b/source/slang/slang-syntax.h index 88a2ca847..8c07855e4 100644 --- a/source/slang/slang-syntax.h +++ b/source/slang/slang-syntax.h @@ -304,22 +304,121 @@ namespace Slang // This fill assert-fail if the object doesn't represent a literal value. IntegerLiteralValue GetIntVal(RefPtr<IntVal> val); - // Represents how much checking has been applied to a declaration. + /// Represents how much checking has been applied to a declaration. enum class DeclCheckState : uint8_t { - // The declaration has been parsed, but not checked + /// The declaration has been parsed, but + /// is otherwise completely unchecked. + /// Unchecked, - // We are in the process of checking the declaration "header" - // (those parts of the declaration needed in order to - // reference it) - CheckingHeader, + /// Basic checks on the modifiers of the declaration have been applied. + /// + /// For example, when a declaration has attributes, the transformation + /// of an attribute from the parsed-but-unchecked form into a checked + /// form (in which it has the appropriate C++ subclass) happens here. + /// + ModifiersChecked, + + /// The declaration's basic signature has been checked to the point that + /// it is ready to be referenced in other places. + /// + /// For a value declaration like a variable or function, this means that + /// the type of the declaration can be queried. + /// + /// For a type declaration like a `struct` or `typedef` this means + /// that a `Type` referring to that declaration can be formed. + /// + ReadyForReference, + + /// The declaration is ready for lookup operations to be performed. + /// + /// For type declarations (e.g., aggregate types, generic type parameters) + /// this means that any base type or constraint clauses have been + /// sufficiently checked so that we can enumerate the inheritance + /// hierarchy of the type and discover all its members. + /// + ReadyForLookup, + + /// Any conformance declared on the declaration have been validated. + /// + /// In particular, this step means that a "witness table" has been + /// created to show how a type satisfies the requirements of any + /// interfaces it conforms to. + /// + ReadyForConformances, + + /// The declaration is fully checked. + /// + /// This step includes any validation of the declaration that is + /// immaterial to clients code using the declaration, but that is + /// nonetheless relevant to checking correctness. + /// + /// The canonical example here is checking the body of functions. + /// Client code cannot depend on *how* a function is implemented, + /// but we still need to (eventually) check the bodies of all + /// functions, so it belongs in the last phase of checking. + /// + Checked, - // We are done checking the declaration header. - CheckedHeader, + // For convenience at sites that call `ensureDecl()`, we define + // some aliases for the above states that are expressed in terms + // of what client code needs to be able to do with a declaration. + // + // These aliases can be changed over time if we decide to add + // more phases to semantic checking. + + CanEnumerateBases = ReadyForLookup, + CanUseBaseOfInheritanceDecl = ReadyForLookup, + CanUseTypeOfValueDecl = ReadyForReference, + CanUseExtensionTargetType = ReadyForLookup, + CanUseAsType = ReadyForReference, + CanUseFuncSignature = ReadyForReference, + CanSpecializeGeneric = ReadyForReference, + CanReadInterfaceRequirements = ReadyForLookup, + }; - // We have checked the declaration fully. - Checked, + /// A `DeclCheckState` plus a bit to track whether a declaration is currently being checked. + struct DeclCheckStateExt + { + public: + DeclCheckStateExt() {} + DeclCheckStateExt(DeclCheckState state) + : m_raw(uint8_t(state)) + {} + + enum : uint8_t + { + /// A flag to indicate that a declaration is being checked. + /// + /// The value of this flag is chosen so that it can be + /// represented in the bits of a `DeclCheckState` without + /// colliding with the bits that represent actual states. + /// + kBeingCheckedBit = 0x80, + }; + + DeclCheckState getState() const { return DeclCheckState(m_raw & ~kBeingCheckedBit); } + void setState(DeclCheckState state) + { + m_raw = (m_raw & kBeingCheckedBit) | uint8_t(state); + } + + bool isBeingChecked() const { return (m_raw & kBeingCheckedBit) != 0; } + + void setIsBeingChecked(bool isBeingChecked) + { + m_raw = (m_raw & ~kBeingCheckedBit) + | (isBeingChecked ? kBeingCheckedBit : 0); + } + + bool operator>=(DeclCheckState state) const + { + return getState() >= state; + } + + private: + uint8_t m_raw = 0; }; void addModifier( |
