From dd435512219f435ea13498e6124930fd4cf823a9 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Mon, 18 Nov 2019 10:36:38 -0800 Subject: Further refactoring of semantic checking (#1102) * Split apart `SemanticsVisitor` The existing `SemanticsVisitor` type was the visitor for expressions, statements, and declarations, and its monolithic nature made it hard to introduce distinct visitors for different phases of checking (despite the fact that we had, de facto, multiple phases of declaration checking). This change splits up `SemanticsVisitor` as follows: * There is nosw a `SharedSemanticsContext` type which holds the shared state that all semantics visiting logic needs. This includes state that gets mutated during the course of semantic checking. * The `SemanticsVisitor` type is now a base class that holds a pointer to a `SharedSemanticsContext`. Most of the non-visitor functions are still defined here, just to keep the code as simple as possible. The `SemanticsVisitor` type is no longer a "visitor" in any meaningful way, but retaining the old name minimizes the diffs to client code. * There are distinct `Semantics{Expr|Stmt|Decl}Visitor` types that have the actual `visit*` methods for an appropriate subset of the AST hierarchy. These all inherit from `SemanticsVisitor` primarily so that they can have easy access to all the helper methods it defines (which used to be accessible because these were all the same object). Any client code that was constructing a `SemanticsVisitor` now needs to construct a `SharedSemanticsContext` and then use that to initialize a `SemanticsVisitor`. Similarly, any code that was using `dispatch()` to invoke the visitor on an AST node needs to construct the appropriate sub-class and then invoke `dispatch()` on it instead. This is a pure refactoring change, so no effort has been made to move state or logic onto the visitor sub-types even when it is logical. Similarly, no attempt has been made to hoist any code out of the common headers to avoid duplication between `.h` and `.cpp` files. Those cleanups will follow. The one cleanup I allowed myself while doing this was getting rid of the `typeResult` member in `SemanticsVisitor` that appears to be a do-nothing field that got written to in a few places (for unclear reasons) but never read. * Remove some statefulness around statement checking Some of the state from the old `SemanticsVisitor` was used in a mutable way during semantic checking: * The `function` field would be set and the restored when checking the body of a function so that things like `return` statements could find the outer function. * The `outerStmts` list was used like a stack to track lexically surrounding statements to resolve things like `break` and `continue` targets. Both of these meant that semantic checking code was doing fine-grained mutations on the shared semantic checking state even though the statefullness wasn't needed. This change moves the relevant state down to `SemanticsStmtVisitor`, which is a type we create on-the-fly to check each statement, so that we now only need to establish the state once at creation time. The list of outer statements is handled as a linked list threaded up through the stack (a recurring idiom through the codebase). There was one place where the `function` field was being used that wasn't strictly inside statement checking: it appears that we were using it to detect whether a variable declaration represents a local, so I added an `_isLocalVar` function to serve the same basic purpose. With this change, the only stateful part of `SharedSemanticsContext` is the information to track imported modules, which seems like a necessary thing (since deduplication requires statefullness). * Refactor declaration checking to avoid recursion The flexiblity of the Slang language makes enforcing ordering on semantic checking difficult. In particular, generics (including some of the built-in standard library types) can take value arguments, so that type expressions can include value expressions. This means that being able to determine the type of a function parameter may require checking expressions, which may in turn require resolving calls to an overloaded function, which in turn requires knowing the types of the parameters of candidate callees. Up to this point there have been two dueling approaches to handling the ordering problem in the semantic checking logic: 1. There was the `EnsureDecl` operation, supported by the `DeclCheckState` type. Every declaration would track "how checked" it is, and `EnsureDecl(d, s)` would try to perform whatever checks are needed to bring declaration `d` up to state `s`. 2. There was top-down orchestration logic in `visitModuleDecl()` that tried to perform checking of declarations in a set of fixed phases that ensure things like all function declarations being checked before any function bodies. Each of these options had problems: 1. The `EnsureDecl()` approach wasn't implemented completely or consistently. It only understood two basic levels of checking: the "header" of a declaration was checked, and then the "body," and it relied on a single `visit*()` routine to try and handle both cases. Things ended up being checked twice, or in a circular fashion. 2. Rather than fix the problems with `EnsureDecl()` we layered on the top-down orchestration logic, but doing so ignores the fact that no fixed set of phases can work for our language. The orchestration logic was also done in a relatively ad hoc fashion that relied on using a single visitor to implement all phases of checking, but it added a second metric of "checked-ness" that worked alongside `DeclCheckState`. This change strives to unify the two worlds and make them consistent. One of the key changes is that instead of doing everything through a single visitor type, we now have distinct visitors for distinct phases of semantic checking, and those phases are one-to-one aligned with the values of the `DeclCheckState` type. More detailed notes: * Existing sites that used to call `checkDecl` to directly invoke semantic checking recursively now use `ensureDecl` instead. This makes sure that `ensureDecl` is the one bottleneck that everything passes through, so that it can guarantee that each phase of checking gets applied to each declaration at most once. * The existing `visitModuleDecl` was revamped into a `checkModule` routine that does the global orchestration, but now it is just a driver routine that makes sure `ensureDecl` gets called on everything in an order that represents an idealized "default schedule" for checking, while not ruling out cases where `ensureDecl()` will change the ordering to handle cases where the global order is insufficient. * Because `checkModule` handles much of the recursion over the declaration hierarchy, many cases where a declaration `visit*()` would recurse on its members have been eliminated. The only case where a declaration should recursively `ensureDecl()` its members is when its validity for a certain phase depends on those members being checked (e.g., determining the type of a function declaration depends on its parameters having been checked). * All cases where a `visit*()` routine was manually checking the state/phase of checking have been eliminated. It is now the responsibility of `ensureDecl` to make sure that checking logic doesn't get invoked twice or in an inappropriate order. * Most cases where a `visit*()` routine was manually *setting* the `DeclCheckState` of a declaration have been eliminated. The common case is now handled by `ensureDecl()` directly, and `visit*()` methods only need to override that logic when special cases arise. E.g., when a variable is declared without a type `(e.g., `let foo = ...;`) then we need to check its initial-value expression to determine its type, so that we must check it further than was initially expected/required. * This change goes to some lengths to try and keep semantic checking logic at the same location in the `slang-check-decl.cpp` file, so each of the per-phase visitor types is forward declared at the top of the file, and then the actual `visit*()` routines are interleaved throughout the rest of the file. A future change could do pure code movement (no semantic changes) to arrive at a more logical organization, but for now I tried to stick with what would minimize the diffs (although the resulting diffs can still be messy at times). * One important change to the semantic checking logic was that the test for use of a local variable ahead of its declaration (or as part of its own initial-value expression) was moved around, since its old location in the middle of the `ensureDecl` logic made the overall flow and intention of that function less clear. There is still a need to fix this check to be more robust in the future. * Add some design documentation on semantic checking The main thing this tries to lay out is the strategy for declaration checking and the rules/constraints on programmers that follow from it. * fixup: typos found during review --- source/slang/slang-check-conformance.cpp | 6 +- source/slang/slang-check-decl.cpp | 1414 ++++++++++++++++-------------- source/slang/slang-check-expr.cpp | 37 +- source/slang/slang-check-impl.h | 391 +++++---- source/slang/slang-check-modifier.cpp | 8 +- source/slang/slang-check-overload.cpp | 10 +- source/slang/slang-check-shader.cpp | 15 +- source/slang/slang-check-stmt.cpp | 144 +-- source/slang/slang-check-type.cpp | 11 +- source/slang/slang-check.cpp | 31 +- source/slang/slang-check.h | 1 + source/slang/slang-lookup.cpp | 20 +- source/slang/slang-lookup.h | 2 +- source/slang/slang-syntax-base-defs.h | 6 +- source/slang/slang-syntax.cpp | 10 +- source/slang/slang-syntax.h | 119 ++- 16 files changed, 1242 insertions(+), 983 deletions(-) (limited to 'source') 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() ) { - checkDecl(aggTypeDeclRef.getDecl()); + ensureDecl(aggTypeDeclRef, DeclCheckState::CanEnumerateBases); for( auto inheritanceDeclRef : getMembersOfTypeWithExt(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(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(SharedSemanticsContext* shared) + : SemanticsDeclVisitorBase(shared) + {} + + void visitDeclGroup(DeclGroup*) {} + + void visitDecl(Decl* decl) + { + checkModifiers(decl); + } + }; + + struct SemanticsDeclHeaderVisitor + : public SemanticsDeclVisitorBase + , public DeclVisitor + { + SemanticsDeclHeaderVisitor(SharedSemanticsContext* shared) + : SemanticsDeclVisitorBase(shared) + {} + + void visitDecl(Decl*) {} + void visitDeclGroup(DeclGroup*) {} + + void checkVarDeclCommon(RefPtr 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(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(SharedSemanticsContext* shared) + : SemanticsDeclVisitorBase(shared) + {} + + void visitDecl(Decl*) {} + void visitDeclGroup(DeclGroup*) {} + + void checkVarDeclCommon(RefPtr 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(pp)) + return true; + + if(auto genericDecl = as(pp)) + pp = genericDecl; + + if(as(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(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, 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(decl)) + for(;;) { - if (auto parenScope = as(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(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(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(decl)) { - decl->SetCheckState(state); + _ensureAllDeclsRec(visitor, genericDecl->inner, state); } } - void SemanticsVisitor::EnusreAllDeclsRec(RefPtr decl) + static bool isUnsizedArrayType(Type* type) { - checkDecl(decl); - if (auto containerDecl = as(decl)) - { - for (auto m : containerDecl->Members) - { - EnusreAllDeclsRec(m); - } - } + // Not an array? + auto arrayType = as(type); + if (!arrayType) return false; + + // Explicit element count given? + auto elementCount = arrayType->ArrayLength; + if (elementCount) return true; + + return true; } - void SemanticsVisitor::CheckVarDeclCommon(RefPtr varDecl) + void SemanticsDeclHeaderVisitor::checkVarDeclCommon(RefPtr 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 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(m)) { - typeParam->initType = CheckProperType(typeParam->initType); + ensureDecl(typeParam, DeclCheckState::ReadyForReference); } else if (auto valParam = as(m)) { - // TODO: some real checking here... - CheckVarDeclCommon(valParam); + ensureDecl(valParam, DeclCheckState::ReadyForReference); } else if (auto constraint = as(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 { - // 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(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(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(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()) { - 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(decl)) + if (auto magicMod = decl->FindModifier()) { - 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(decl)) { - auto inner = decl; - if (auto genericDecl = as(decl)) + for(auto childDecl : containerDecl->Members) { - inner = genericDecl->inner; - } + if(as(childDecl)) + continue; - if (auto builtinMod = inner->FindModifier()) - { - registerBuiltinDecl(getSession(), decl, builtinMod); - } - if (auto magicMod = inner->FindModifier()) - { - 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()) - { - checkDecl(importDecl); - } - // register all extensions - for (auto & s : programNode->getMembersOfType()) - registerExtension(s); - for (auto & g : programNode->getMembersOfType()) + if(auto genericDecl = as(decl)) { - if (auto extDecl = as(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(decl)) - { - bool isTypeAttributeClass = false; - for (auto attrib : typeMember->GetModifiersOfType()) - { - if (attrib->name == getSession()->getNameObj("AttributeUsageAttribute")) - { - isTypeAttributeClass = true; - break; - } - } - if (isTypeAttributeClass) - checkDecl(decl); - } + _registerBuiltinDeclsRec(getSession(), moduleDecl); } - // check types - for (auto & s : programNode->getMembersOfType()) - 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()) { - checkingPhase = pass == 0 ? CheckingPhase::Header : CheckingPhase::Body; - - for (auto & s : programNode->getMembersOfType()) - { - checkDecl(s.Ptr()); - } - // HACK(tfoley): Visiting all generic declarations here, - // because otherwise they won't get visited. - for (auto & g : programNode->getMembersOfType()) - { - checkDecl(g.Ptr()); - } - - // before checking conformance, make sure we check all the extension bodies - // generic extension decls are already checked by the loop above - for (auto & s : programNode->getMembersOfType()) - checkDecl(s); - - for (auto & func : programNode->getMembersOfType()) - { - if (!func->IsChecked(getCheckedState())) - { - VisitFunctionDeclaration(func.Ptr()); - } - } - for (auto & func : programNode->getMembersOfType()) - { - checkDecl(func); - } - - if (getSink()->GetErrorCount() != 0) - return; - - // Force everything to be fully checked, just in case - // Note that we don't just call this on the program, - // because we'd end up recursing into this very code path... - for (auto d : programNode->Members) - { - EnusreAllDeclsRec(d); - } + 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` 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()) { - 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()) { - 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() ) { - 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 tagType; - InheritanceDecl* tagTypeInheritanceDecl = nullptr; - for(auto inheritanceDecl : decl->getMembersOfType()) - { - 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 tagType; + InheritanceDecl* tagTypeInheritanceDecl = nullptr; + for(auto inheritanceDecl : decl->getMembersOfType()) + { + 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(superType)) + if(auto errorType = as(superType)) + { + // Ignore any erroneous inheritance clauses. + continue; + } + else if(auto declRefType = as(superType)) + { + if(auto interfaceDeclRef = declRefType->declRef.as()) { - // Ignore any erroneous inheritance clauses. + // Don't consider interface bases as candidates for + // the tag type. continue; } - else if(auto declRefType = as(superType)) - { - if(auto interfaceDeclRef = declRefType->declRef.as()) - { - // Don't consider interface bases as candidates for - // the tag type. - continue; - } - } - - if(tagType) - { - // We already found a tag type. - getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType); - getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType); - break; - } - else - { - tagType = superType; - tagTypeInheritanceDecl = inheritanceDecl; - } } - // If a tag type has not been set, then we - // default it to the built-in `int` type. - // - // TODO: In the far-flung future we may want to distinguish - // `enum` types that have a "raw representation" like this from - // ones that are purely abstract and don't expose their - // type of their tag. - if(!tagType) + 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 enumTypeType = getSession()->getEnumTypeType(); - - RefPtr enumConformanceDecl = new InheritanceDecl(); - enumConformanceDecl->ParentDecl = decl; - enumConformanceDecl->loc = decl->loc; - enumConformanceDecl->base.type = getSession()->getEnumTypeType(); - decl->Members.add(enumConformanceDecl); - - // The `__EnumType` interface has one required member, the `__Tag` type. - // We need to satisfy this requirement automatically, rather than require - // the user to actually declare a member with this name (otherwise we wouldn't - // let them define a tag value with the name `__Tag`). - // - RefPtr witnessTable = new WitnessTable(); - enumConformanceDecl->witnessTable = witnessTable; - Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag"); - Decl* tagAssociatedTypeDecl = nullptr; - if(auto enumTypeTypeDeclRefType = enumTypeType.dynamicCast()) + // An `enum` type should automatically conform to the `__EnumType` interface. + // The compiler needs to insert this conformance behind the scenes, and this + // seems like the best place to do it. + { + // First, look up the type of the `__EnumType` interface. + RefPtr enumTypeType = getSession()->getEnumTypeType(); + + RefPtr enumConformanceDecl = new InheritanceDecl(); + enumConformanceDecl->ParentDecl = decl; + enumConformanceDecl->loc = decl->loc; + enumConformanceDecl->base.type = getSession()->getEnumTypeType(); + decl->Members.add(enumConformanceDecl); + + // The `__EnumType` interface has one required member, the `__Tag` type. + // We need to satisfy this requirement automatically, rather than require + // the user to actually declare a member with this name (otherwise we wouldn't + // let them define a tag value with the name `__Tag`). + // + RefPtr witnessTable = new WitnessTable(); + enumConformanceDecl->witnessTable = witnessTable; + + Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag"); + Decl* tagAssociatedTypeDecl = nullptr; + if(auto enumTypeTypeDeclRefType = enumTypeType.dynamicCast()) + { + if(auto enumTypeTypeInterfaceDecl = as(enumTypeTypeDeclRefType->declRef.getDecl())) { - if(auto enumTypeTypeInterfaceDecl = as(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()) - { - // 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()) + { + // 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()) + // For any enum case that didn't provide an explicit + // tag value, derived an appropriate tag value. + IntegerLiteralValue defaultTag = 0; + for(auto caseDecl : decl->getMembersOfType()) + { + if(auto explicitTagValExpr = caseDecl->tagExpr) { - 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 explicitTagVal = TryConstantFoldExpr(explicitTagValExpr); - if(explicitTagVal) + RefPtr explicitTagVal = TryConstantFoldExpr(explicitTagValExpr); + if(explicitTagVal) + { + if(auto constIntVal = as(explicitTagVal)) { - if(auto constIntVal = as(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 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(memberDecl)) - continue; - - // Already checked enum case declarations above. - if(auto caseDecl = as(memberDecl)) - continue; + // This tag has no initializer, so it should use + // the default tag value we are tracking. + RefPtr tagValExpr = new IntegerLiteralExpr(); + tagValExpr->loc = caseDecl->loc; + tagValExpr->type = QualType(tagType); + tagValExpr->value = defaultTag; - // 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(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(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(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(declBase)) { - decl->SetCheckState(DeclCheckState::CheckedHeader); - // global generic param only allowed in global scope - auto program = as(decl->ParentDecl); - if (!program) - getSink()->diagnose(decl, Slang::Diagnostics::globalGenParamInGlobalScopeOnly); - // Now check all of the member declarations. - for (auto member : decl->Members) + 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(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(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(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 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 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(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(decl->targetType)) - { - getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here"); - } - // now check the members of the extension - for (auto m : decl->Members) - { - checkDecl(m); - } - decl->SetCheckState(getCheckedState()); - } - RefPtr SemanticsVisitor::findResultTypeForConstructorDecl(ConstructorDecl* decl) { // We want to look at the parent of the declaration, @@ -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(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(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 declRef) { + RefPtr 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 SemanticsVisitor::CheckTerm(RefPtr term) { if (!term) return nullptr; - return ExprVisitor::dispatch(term); + + SemanticsExprVisitor exprVisitor(getShared()); + return exprVisitor.dispatch(term); } RefPtr SemanticsVisitor::CreateErrorExpr(Expr* expr) @@ -448,13 +450,13 @@ namespace Slang return nullptr; } - RefPtr SemanticsVisitor::visitBoolLiteralExpr(BoolLiteralExpr* expr) + RefPtr SemanticsExprVisitor::visitBoolLiteralExpr(BoolLiteralExpr* expr) { expr->type = getSession()->getBoolType(); return expr; } - RefPtr SemanticsVisitor::visitIntegerLiteralExpr(IntegerLiteralExpr* expr) + RefPtr 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 SemanticsVisitor::visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) + RefPtr SemanticsExprVisitor::visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) { if(!expr->type.type) { @@ -483,7 +485,7 @@ namespace Slang return expr; } - RefPtr SemanticsVisitor::visitStringLiteralExpr(StringLiteralExpr* expr) + RefPtr SemanticsExprVisitor::visitStringLiteralExpr(StringLiteralExpr* expr) { expr->type = getSession()->getStringType(); return expr; @@ -766,7 +768,7 @@ namespace Slang return subscriptExpr; } - RefPtr SemanticsVisitor::visitIndexExpr(IndexExpr* subscriptExpr) + RefPtr 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 SemanticsVisitor::visitParenExpr(ParenExpr* expr) + RefPtr SemanticsExprVisitor::visitParenExpr(ParenExpr* expr) { auto base = expr->base; base = CheckTerm(base); @@ -920,7 +921,7 @@ namespace Slang } } - RefPtr SemanticsVisitor::visitAssignExpr(AssignExpr* expr) + RefPtr SemanticsExprVisitor::visitAssignExpr(AssignExpr* expr) { expr->left = CheckExpr(expr->left); @@ -1032,7 +1033,7 @@ namespace Slang return rs; } - RefPtr SemanticsVisitor::visitInvokeExpr(InvokeExpr *expr) + RefPtr SemanticsExprVisitor::visitInvokeExpr(InvokeExpr *expr) { // check the base expression first expr->FunctionExpr = CheckExpr(expr->FunctionExpr); @@ -1045,7 +1046,7 @@ namespace Slang return CheckInvokeExprWithCheckedOperands(expr); } - RefPtr SemanticsVisitor::visitVarExpr(VarExpr *expr) + RefPtr 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 SemanticsVisitor::visitTypeCastExpr(TypeCastExpr * expr) + RefPtr 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 SemanticsVisitor::visitStaticMemberExpr(StaticMemberExpr* expr) + RefPtr SemanticsExprVisitor::visitStaticMemberExpr(StaticMemberExpr* expr) { expr->BaseExpression = CheckExpr(expr->BaseExpression); @@ -1452,7 +1453,7 @@ namespace Slang return expr; } - RefPtr SemanticsVisitor::visitMemberExpr(MemberExpr * expr) + RefPtr SemanticsExprVisitor::visitMemberExpr(MemberExpr * expr) { expr->BaseExpression = CheckExpr(expr->BaseExpression); @@ -1521,7 +1522,7 @@ namespace Slang } } - RefPtr SemanticsVisitor::visitInitializerListExpr(InitializerListExpr* expr) + RefPtr 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 SemanticsVisitor::visitThisExpr(ThisExpr* expr) + RefPtr 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(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(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 conversionCostCache; }; - enum class CheckingPhase + /// Shared state for a semantics-checking session. + struct SharedSemanticsContext { - Header, Body - }; - - struct SemanticsVisitor - : ExprVisitor> - , StmtVisitor - , DeclVisitor - { - 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 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 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 typeResult; RefPtr TranslateTypeNodeImpl(const RefPtr & node); RefPtr ExtractTypeFromTypeRepr(const RefPtr& typeRepr); @@ -367,19 +380,38 @@ namespace Slang DeclRef genericDeclRef, List> 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, 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); + /// 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 toType, RefPtr fromExpr); - void CheckVarDeclCommon(RefPtr 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 checkConstantIntVal( RefPtr expr); @@ -626,14 +646,6 @@ namespace Slang RefPtr 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 satisfyingMemberDeclRef, DeclRef 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 - T* FindOuterStmt(); - - void visitBreakStmt(BreakStmt *stmt); - - void visitContinueStmt(ContinueStmt *stmt); - - void PushOuterStmt(Stmt* stmt); - - void PopOuterStmt(Stmt* /*stmt*/); - RefPtr checkPredicateExpr(Expr* expr); - void visitDoWhileStmt(DoWhileStmt *stmt); - - void visitForStmt(ForStmt *stmt); - RefPtr checkExpressionAndExpectIntegerConstant(RefPtr expr, RefPtr* 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 val); @@ -842,22 +789,8 @@ namespace Slang void validateArraySizeForVariable(VarDeclBase* varDecl); - void visitVarDecl(VarDecl* varDecl); - - void visitWhileStmt(WhileStmt *stmt); - - void visitExpressionStmt(ExpressionStmt *stmt); - - RefPtr visitBoolLiteralExpr(BoolLiteralExpr* expr); - RefPtr visitIntegerLiteralExpr(IntegerLiteralExpr* expr); - RefPtr visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr); - RefPtr 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 elementType, RefPtr elementCount); - RefPtr visitIndexExpr(IndexExpr* subscriptExpr); - - RefPtr 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 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 findResultTypeForConstructorDecl(ConstructorDecl* decl); - void visitConstructorDecl(ConstructorDecl* decl); - - - void visitSubscriptDecl(SubscriptDecl* decl); - - void visitAccessorDecl(AccessorDecl* decl); - // @@ -1308,28 +1226,83 @@ namespace Slang RefPtr baseExpr, OverloadResolveContext& context); - RefPtr visitGenericAppExpr(GenericAppExpr* genericAppExpr); - /// Check a generic application where the operands have already been checked. RefPtr checkGenericAppWithCheckedArgs(GenericAppExpr* genericAppExpr); - RefPtr visitSharedTypeExpr(SharedTypeExpr* expr); - - RefPtr visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr); - RefPtr CheckExpr(RefPtr expr); RefPtr CheckInvokeExprWithCheckedOperands(InvokeExpr *expr); + // Get the type to use when referencing a declaration + QualType GetTypeForDeclRef(DeclRef declRef); + + // + // + // + + RefPtr MaybeDereference(RefPtr inExpr); + + RefPtr CheckSwizzleExpr( + MemberExpr* memberRefExpr, + RefPtr baseElementType, + IntegerLiteralValue baseElementCount); + + RefPtr CheckSwizzleExpr( + MemberExpr* memberRefExpr, + RefPtr baseElementType, + RefPtr 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 _lookupStaticMember(RefPtr expr, RefPtr baseExpression); + + RefPtr visitStaticMemberExpr(StaticMemberExpr* expr); + + RefPtr lookupMemberResultFailure( + DeclRefExpr* expr, + QualType const& baseType); + + SharedSemanticsContext & operator = (const SharedSemanticsContext &) = delete; + + + // + + void importModuleIntoScope(Scope* scope, ModuleDecl* moduleDecl); + }; + + struct SemanticsExprVisitor + : public SemanticsVisitor + , ExprVisitor> + { + public: + SemanticsExprVisitor(SharedSemanticsContext* shared) + : SemanticsVisitor(shared) + {} + + RefPtr visitBoolLiteralExpr(BoolLiteralExpr* expr); + RefPtr visitIntegerLiteralExpr(IntegerLiteralExpr* expr); + RefPtr visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr); + RefPtr visitStringLiteralExpr(StringLiteralExpr* expr); + + RefPtr visitIndexExpr(IndexExpr* subscriptExpr); + + RefPtr visitParenExpr(ParenExpr* expr); + + RefPtr visitAssignExpr(AssignExpr* expr); + + RefPtr visitGenericAppExpr(GenericAppExpr* genericAppExpr); + + RefPtr visitSharedTypeExpr(SharedTypeExpr* expr); + + RefPtr visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr); + RefPtr visitInvokeExpr(InvokeExpr *expr); RefPtr visitVarExpr(VarExpr *expr); RefPtr visitTypeCastExpr(TypeCastExpr * expr); - // Get the type to use when referencing a declaration - QualType GetTypeForDeclRef(DeclRef 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 visitStaticMemberExpr(StaticMemberExpr* expr); - RefPtr MaybeDereference(RefPtr inExpr); + RefPtr visitMemberExpr(MemberExpr * expr); - RefPtr CheckSwizzleExpr( - MemberExpr* memberRefExpr, - RefPtr baseElementType, - IntegerLiteralValue baseElementCount); + RefPtr visitInitializerListExpr(InitializerListExpr* expr); - RefPtr CheckSwizzleExpr( - MemberExpr* memberRefExpr, - RefPtr baseElementType, - RefPtr baseElementCount); + RefPtr 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 _lookupStaticMember(RefPtr expr, RefPtr baseExpression); + struct SemanticsStmtVisitor + : public SemanticsVisitor + , StmtVisitor + { + SemanticsStmtVisitor(SharedSemanticsContext* shared, FuncDecl* parentFunc, OuterStmtInfo* outerStmts) + : SemanticsVisitor(shared) + , m_parentFunc(parentFunc) + , m_outerStmts(outerStmts) + {} - RefPtr 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 lookupMemberResultFailure( - DeclRefExpr* expr, - QualType const& baseType); + /// The linked list of lexically surrounding statements. + OuterStmtInfo* m_outerStmts = nullptr; - RefPtr visitMemberExpr(MemberExpr * expr); + FuncDecl* getParentFunc() { return m_parentFunc; } - SemanticsVisitor & operator = (const SemanticsVisitor &) = delete; + void checkStmt(Stmt* stmt); + template + T* FindOuterStmt(); - // + void visitDeclStmt(DeclStmt* stmt); - RefPtr 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 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(attr)) { + // check arguments against attribute parameters defined in attribClassDecl Index paramIndex = 0; auto params = attribClassDecl->getMembersOfType(); 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 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 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()) { - checkDecl(genericDeclRef.getDecl()); + ensureDecl(genericDeclRef, DeclCheckState::CanSpecializeGeneric); OverloadCandidate candidate; candidate.flavor = OverloadCandidate::Flavor::Generic; @@ -1376,7 +1376,7 @@ namespace Slang } } - RefPtr SemanticsVisitor::visitGenericAppExpr(GenericAppExpr* genericAppExpr) + RefPtr 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 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 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 - 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(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(); if (!outer) @@ -64,7 +98,7 @@ namespace Slang stmt->parentStmt = outer; } - void SemanticsVisitor::visitContinueStmt(ContinueStmt *stmt) + void SemanticsStmtVisitor::visitContinueStmt(ContinueStmt *stmt) { auto outer = FindOuterStmt(); 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 SemanticsVisitor::checkPredicateExpr(Expr* expr) { RefPtr 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 SemanticsVisitor::checkExpressionAndExpectIntegerConstant(RefPtr expr, RefPtr* 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(); 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> args; for (RefPtr member : genericDeclRef.getDecl()->Members) { @@ -331,7 +334,7 @@ namespace Slang declRef).as(); } - RefPtr SemanticsVisitor::visitSharedTypeExpr(SharedTypeExpr* expr) + RefPtr SemanticsExprVisitor::visitSharedTypeExpr(SharedTypeExpr* expr) { if (!expr->type.Ptr()) { @@ -341,7 +344,7 @@ namespace Slang return expr; } - RefPtr SemanticsVisitor::visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr) + RefPtr 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 ApplyExtensionToType( - SemanticsVisitor* semantics, + SemanticsVisitor* semantics, ExtensionDecl* extDecl, RefPtr type); @@ -241,6 +241,11 @@ DeclRef maybeSpecializeInterfaceDeclRef( { if (auto superInterfaceDeclRef = superTypeDeclRef.as()) { + // 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() ) { + if (request.semantics) + { + ensureDecl(request.semantics, containerDeclRef.getDecl(), DeclCheckState::ReadyForLookup); + } + RefPtr type = DeclRefType::Create( session, aggTypeDeclRef); @@ -397,6 +407,8 @@ void DoLocalLookupImpl( RefPtr targetDeclRefType; if (auto extDeclRef = containerDeclRef.as()) { + ensureDecl(request.semantics, extDeclRef.getDecl(), DeclCheckState::CanUseExtensionTargetType); + targetDeclRefType = as(extDeclRef.getDecl()->targetType); SLANG_ASSERT(targetDeclRefType); int diff = 0; @@ -414,7 +426,7 @@ void DoLocalLookupImpl( auto baseInterfaces = getMembersOfType(containerDeclRef); for (auto inheritanceDeclRef : baseInterfaces) { - checkDecl(request.semantics, inheritanceDeclRef.decl); + ensureDecl(request.semantics, inheritanceDeclRef.getDecl(), DeclCheckState::CanUseBaseOfInheritanceDecl); auto baseType = inheritanceDeclRef.getDecl()->base.type.dynamicCast(); SLANG_ASSERT(baseType); @@ -556,7 +568,7 @@ LookupResult lookUpLocal( SemanticsVisitor* semantics, Name* name, DeclRef 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 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, RefPtr 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(decl->ParentDecl)) + declToRegister = genericDecl; + + session->magicDecls[modifier->name] = declToRegister.Ptr(); } RefPtr 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 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( -- cgit v1.2.3