summaryrefslogtreecommitdiff
path: root/source/slang/slang-check-decl.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2019-11-18 10:36:38 -0800
committerGitHub <noreply@github.com>2019-11-18 10:36:38 -0800
commitdd435512219f435ea13498e6124930fd4cf823a9 (patch)
tree3567c13db841576e8ee11d9ec119ca6adfa6ddcc /source/slang/slang-check-decl.cpp
parent1123ff2349769744328772888a08fa6c41e502bf (diff)
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
Diffstat (limited to 'source/slang/slang-check-decl.cpp')
-rw-r--r--source/slang/slang-check-decl.cpp1414
1 files changed, 760 insertions, 654 deletions
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index 581cefdcb..879f1c5fa 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -14,6 +14,114 @@
namespace Slang
{
+ /// Visitor to transition declarations to `DeclCheckState::CheckedModifiers`
+ struct SemanticsDeclModifiersVisitor
+ : public SemanticsDeclVisitorBase
+ , public DeclVisitor<SemanticsDeclModifiersVisitor>
+ {
+ SemanticsDeclModifiersVisitor(SharedSemanticsContext* shared)
+ : SemanticsDeclVisitorBase(shared)
+ {}
+
+ void visitDeclGroup(DeclGroup*) {}
+
+ void visitDecl(Decl* decl)
+ {
+ checkModifiers(decl);
+ }
+ };
+
+ struct SemanticsDeclHeaderVisitor
+ : public SemanticsDeclVisitorBase
+ , public DeclVisitor<SemanticsDeclHeaderVisitor>
+ {
+ SemanticsDeclHeaderVisitor(SharedSemanticsContext* shared)
+ : SemanticsDeclVisitorBase(shared)
+ {}
+
+ void visitDecl(Decl*) {}
+ void visitDeclGroup(DeclGroup*) {}
+
+ void checkVarDeclCommon(RefPtr<VarDeclBase> varDecl);
+
+ void visitVarDecl(VarDecl* varDecl)
+ {
+ checkVarDeclCommon(varDecl);
+ }
+
+ void visitImportDecl(ImportDecl* decl);
+
+ void visitGenericTypeParamDecl(GenericTypeParamDecl* decl);
+
+ void visitGenericValueParamDecl(GenericValueParamDecl* decl);
+
+ void visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl);
+
+ void visitGenericDecl(GenericDecl* genericDecl);
+
+ void visitTypeDefDecl(TypeDefDecl* decl);
+
+ void visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl);
+
+ void visitAssocTypeDecl(AssocTypeDecl* decl);
+
+ void visitFuncDecl(FuncDecl* funcDecl);
+
+ void visitParamDecl(ParamDecl* paramDecl);
+
+ void visitConstructorDecl(ConstructorDecl* decl);
+
+ void visitSubscriptDecl(SubscriptDecl* decl);
+
+ void visitAccessorDecl(AccessorDecl* decl);
+ };
+
+ struct SemanticsDeclBasesVisitor
+ : public SemanticsDeclVisitorBase
+ , public DeclVisitor<SemanticsDeclBasesVisitor>
+ {
+ SemanticsDeclBasesVisitor(SharedSemanticsContext* shared)
+ : SemanticsDeclVisitorBase(shared)
+ {}
+
+ void visitDecl(Decl*) {}
+ void visitDeclGroup(DeclGroup*) {}
+
+ void visitInheritanceDecl(InheritanceDecl* inheritanceDecl);
+
+ void visitAggTypeDecl(AggTypeDecl* decl);
+
+ void visitEnumDecl(EnumDecl* decl);
+
+ void visitExtensionDecl(ExtensionDecl* decl);
+ };
+
+ struct SemanticsDeclBodyVisitor
+ : public SemanticsDeclVisitorBase
+ , public DeclVisitor<SemanticsDeclBodyVisitor>
+ {
+ SemanticsDeclBodyVisitor(SharedSemanticsContext* shared)
+ : SemanticsDeclVisitorBase(shared)
+ {}
+
+ void visitDecl(Decl*) {}
+ void visitDeclGroup(DeclGroup*) {}
+
+ void checkVarDeclCommon(RefPtr<VarDeclBase> varDecl);
+
+ void visitVarDecl(VarDecl* varDecl)
+ {
+ checkVarDeclCommon(varDecl);
+ }
+
+ void visitEnumCaseDecl(EnumCaseDecl* decl);
+
+ void visitEnumDecl(EnumDecl* decl);
+
+ void visitFuncDecl(FuncDecl* funcDecl);
+
+ void visitParamDecl(ParamDecl* paramDecl);
+ };
/// Should the given `decl` nested in `parentDecl` be treated as a static rather than instance declaration?
bool isEffectivelyStatic(
@@ -104,6 +212,22 @@ namespace Slang
return true;
}
+ static bool _isLocalVar(VarDeclBase* varDecl)
+ {
+ auto pp = varDecl->ParentDecl;
+
+ if(as<ScopeDecl>(pp))
+ return true;
+
+ if(auto genericDecl = as<GenericDecl>(pp))
+ pp = genericDecl;
+
+ if(as<FuncDecl>(pp))
+ return true;
+
+ return false;
+ }
+
// Get the type to use when referencing a declaration
QualType getTypeForDeclRef(
Session* session,
@@ -114,7 +238,40 @@ namespace Slang
{
if( sema )
{
- sema->checkDecl(declRef.getDecl());
+ // Hack: if we are somehow referencing a local variable declaration
+ // before the line of code that defines it, then we need to diagnose
+ // an error.
+ //
+ // TODO: The right answer is that lookup should have been performed in
+ // the scope that was in place *before* the variable was declared, but
+ // this is a quick fix that at least alerts the user to how we are
+ // interpreting their code.
+ //
+ // We detect the problematic case by looking for an attempt to reference
+ // a local variable declaration when it is unchecked, or in the process
+ // of being checked (the latter case catches a local variable that refers
+ // to itself in its initial-value expression).
+ //
+ auto checkStateExt = declRef.getDecl()->checkState;
+ if( checkStateExt.getState() == DeclCheckState::Unchecked
+ || checkStateExt.isBeingChecked() )
+ {
+ if(auto varDecl = as<VarDecl>(declRef.getDecl()))
+ {
+ if(_isLocalVar(varDecl))
+ {
+ sema->getSink()->diagnose(varDecl, Diagnostics::localVariableUsedBeforeDeclared, varDecl);
+ return QualType(session->getErrorType());
+ }
+ }
+ }
+
+ // Once we've rules out the case of referencing a local declaration
+ // before it has been checked, we will go ahead and ensure that
+ // semantic checking has been performed on the chosen declaration,
+ // at least up to the point where we can query its type.
+ //
+ sema->ensureDecl(declRef, DeclCheckState::CanUseTypeOfValueDecl);
}
// We need to insert an appropriate type for the expression, based on
@@ -313,9 +470,9 @@ namespace Slang
return subst;
}
- void checkDecl(SemanticsVisitor* visitor, Decl* decl)
+ void ensureDecl(SemanticsVisitor* visitor, Decl* decl, DeclCheckState state)
{
- visitor->checkDecl(decl);
+ visitor->ensureDecl(decl, state);
}
bool SemanticsVisitor::isDeclUsableAsStaticMember(
@@ -381,13 +538,28 @@ namespace Slang
return isDeclUsableAsStaticMember(decl);
}
+ /// Dispatch an appropriate visitor to check `decl` up to state `state`
+ ///
+ /// The current state of `decl` must be `state-1`.
+ /// This call does *not* handle updating the state of `decl`; the
+ /// caller takes responsibility for doing so.
+ ///
+ static void _dispatchDeclCheckingVisitor(Decl* decl, DeclCheckState state, SharedSemanticsContext* shared);
+
// Make sure a declaration has been checked, so we can refer to it.
// Note that this may lead to us recursively invoking checking,
// so this may not be the best way to handle things.
- void SemanticsVisitor::EnsureDecl(RefPtr<Decl> decl, DeclCheckState state)
+ void SemanticsVisitor::ensureDecl(Decl* decl, DeclCheckState state)
{
+ // If the `decl` has already been checked up to or beyond `state`
+ // then there is nothing for us to do.
+ //
if (decl->IsChecked(state)) return;
- if (decl->checkState == DeclCheckState::CheckingHeader)
+
+ // Is the declaration already being checked, somewhere up the
+ // call stack from us?
+ //
+ if(decl->checkState.isBeingChecked())
{
// We tried to reference the same declaration while checking it!
//
@@ -399,58 +571,130 @@ namespace Slang
return;
}
- // Hack: if we are somehow referencing a local variable declaration
- // before the line of code that defines it, then we need to diagnose
- // an error.
+ // Set the flag that indicates we are checking this declaration,
+ // so that the cycle check above will catch us before we go
+ // into any infinite loops.
+ //
+ decl->checkState.setIsBeingChecked(true);
+
+ // Our task is to bring the `decl` up to `state` which may be
+ // one or more steps ahead of where it currently is. We can
+ // invoke a visitor designed to bring a declaration from state
+ // N to state N+1, and in general we might need multiple such
+ // passes to get `decl` to where we need it.
//
- // TODO: The right answer is that lookup should have been performed in
- // the scope that was in place *before* the variable was declared, but
- // this is a quick fix that at least alerts the user to how we are
- // interpreting their code.
+ // The coding of this loop is somewhat defensive to deal
+ // with special cases that will be described along the way.
//
- if (auto varDecl = as<VarDecl>(decl))
+ for(;;)
{
- if (auto parenScope = as<ScopeDecl>(varDecl->ParentDecl))
+ // The first thing is to check what state the decl is
+ // currently in at the start of this loop iteration,
+ // and to bail out if it has been checked up to
+ // (or beyond) our target state.
+ //
+ auto currentState = decl->checkState.getState();
+ if(currentState >= state)
+ break;
+
+ // Because our visitors are only designed to go from state
+ // N to N+1 in general, we will aspire to transition to
+ // a state that is one greater than `currentState`.
+ //
+ auto nextState = DeclCheckState(Int(currentState) + 1);
+
+ // We now dispatch an appropriate visitor based on `nextState`.
+ //
+ _dispatchDeclCheckingVisitor(decl, nextState, getShared());
+
+ // In the common case, the visitor will have done the necessary
+ // checking, but will *not* have updated the `checkState` on
+ // `decl`. In that case we will do the update here, to save
+ // us the complication of having to deal with state update in
+ // every single visitor method.
+ //
+ // However, sometimes a visitor *will* want to manually update
+ // the state of a declaration, and it may actually update it
+ // *past* the `nextState` we asked for (or even past the
+ // eventual target `state`). In those cases we don't want to
+ // accidentally set the state of `decl` to something lower
+ // than what has actually been checked, so we test for
+ // such cases here.
+ //
+ if(nextState > decl->checkState.getState())
{
- // TODO: This diagnostic should be emitted on the line that is referencing
- // the declaration. That requires `EnsureDecl` to take the requesting
- // location as a parameter.
- getSink()->diagnose(decl, Diagnostics::localVariableUsedBeforeDeclared, decl);
- return;
+ decl->SetCheckState(nextState);
}
}
- if (DeclCheckState::CheckingHeader > decl->checkState)
- {
- decl->SetCheckState(DeclCheckState::CheckingHeader);
- }
+ // Once we are done here, the state of `decl` should have
+ // been upgraded to (at least) `state`.
+ //
+ SLANG_ASSERT(decl->IsChecked(state));
- // Check the modifiers on the declaration first, in case
- // semantics of the body itself will depend on them.
- checkModifiers(decl);
+ // Now that we are done checking `decl` we need to restore
+ // its "is being checked" flag so that we don't generate
+ // errors the next time somebody calls `ensureDecl()` on it.
+ //
+ decl->checkState.setIsBeingChecked(false);
+ }
+
+ /// Recursively ensure the tree of declarations under `decl` is in `state`.
+ ///
+ /// This function does *not* handle declarations nested in function bodies
+ /// because those cannot be meaningfully checked outside of the context
+ /// of their surrounding statement(s).
+ ///
+ static void _ensureAllDeclsRec(
+ SemanticsDeclVisitorBase* visitor,
+ Decl* decl,
+ DeclCheckState state)
+ {
+ // Ensure `decl` itself first.
+ visitor->ensureDecl(decl, state);
+
+ // If `decl` is a container, then we want to ensure its children.
+ if(auto containerDecl = as<ContainerDecl>(decl))
+ {
+ // As an exception, if any of the child is a `ScopeDecl`,
+ // then that indicates that it represents a scope for local
+ // declarations under a statement (e.g., in a function body),
+ // and we don't want to check such local declarations here.
+ //
+ for(auto childDecl : containerDecl->Members)
+ {
+ if(as<ScopeDecl>(childDecl))
+ continue;
- // Use visitor pattern to dispatch to correct case
- dispatchDecl(decl);
+ _ensureAllDeclsRec(visitor, childDecl, state);
+ }
+ }
- if(state > decl->checkState)
+ // Note: the "inner" declaration of a `GenericDecl` is currently
+ // not exposed as one of its children (despite a `GenericDecl`
+ // being a `ContainerDecl`), so we need to handle the inner
+ // declaration of a generic as another case here.
+ //
+ if(auto genericDecl = as<GenericDecl>(decl))
{
- decl->SetCheckState(state);
+ _ensureAllDeclsRec(visitor, genericDecl->inner, state);
}
}
- void SemanticsVisitor::EnusreAllDeclsRec(RefPtr<Decl> decl)
+ static bool isUnsizedArrayType(Type* type)
{
- checkDecl(decl);
- if (auto containerDecl = as<ContainerDecl>(decl))
- {
- for (auto m : containerDecl->Members)
- {
- EnusreAllDeclsRec(m);
- }
- }
+ // Not an array?
+ auto arrayType = as<ArrayExpressionType>(type);
+ if (!arrayType) return false;
+
+ // Explicit element count given?
+ auto elementCount = arrayType->ArrayLength;
+ if (elementCount) return true;
+
+ return true;
}
- void SemanticsVisitor::CheckVarDeclCommon(RefPtr<VarDeclBase> varDecl)
+ void SemanticsDeclHeaderVisitor::checkVarDeclCommon(RefPtr<VarDeclBase> varDecl)
{
// A variable that didn't have an explicit type written must
// have its type inferred from the initial-value expression.
@@ -481,21 +725,29 @@ namespace Slang
varDecl->type.type = initExpr->type;
}
+ // If we've gone down this path, then the variable
+ // declaration is actually pretty far along in checking
varDecl->SetCheckState(DeclCheckState::Checked);
}
else
{
- if (function || checkingPhase == CheckingPhase::Header)
+ // A variable with an explicit type is simpler, for the
+ // most part.
+
+ TypeExp typeExp = CheckUsableType(varDecl->type);
+ varDecl->type = typeExp;
+ if (varDecl->type.Equals(getSession()->getVoidType()))
{
- TypeExp typeExp = CheckUsableType(varDecl->type);
- varDecl->type = typeExp;
- if (varDecl->type.Equals(getSession()->getVoidType()))
- {
- getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid);
- }
+ getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid);
}
- if (checkingPhase == CheckingPhase::Body)
+ // If this is an unsized array variable, then we first want to give
+ // it a chance to infer an array size from its initializer
+ //
+ // TODO(tfoley): May need to extend this to handle the
+ // multi-dimensional case...
+ //
+ if(isUnsizedArrayType(varDecl->type))
{
if (auto initExpr = varDecl->initExpr)
{
@@ -503,25 +755,29 @@ namespace Slang
initExpr = coerce(varDecl->type.Ptr(), initExpr);
varDecl->initExpr = initExpr;
- // If this is an array variable, then we first want to give
- // it a chance to infer an array size from its initializer
- //
- // TODO(tfoley): May need to extend this to handle the
- // multi-dimensional case...
- //
maybeInferArraySizeForVariable(varDecl);
- //
- // Next we want to make sure that the declared (or inferred)
- // size for the array meets whatever language-specific
- // constraints we want to enforce (e.g., disallow empty
- // arrays in specific cases)
- //
- validateArraySizeForVariable(varDecl);
+
+ varDecl->SetCheckState(DeclCheckState::Checked);
}
}
+ //
+ // Next we want to make sure that the declared (or inferred)
+ // size for the array meets whatever language-specific
+ // constraints we want to enforce (e.g., disallow empty
+ // arrays in specific cases)
+ //
+ validateArraySizeForVariable(varDecl);
+ }
+ }
+ void SemanticsDeclBodyVisitor::checkVarDeclCommon(RefPtr<VarDeclBase> varDecl)
+ {
+ if (auto initExpr = varDecl->initExpr)
+ {
+ initExpr = CheckTerm(initExpr);
+ initExpr = coerce(varDecl->type.Ptr(), initExpr);
+ varDecl->initExpr = initExpr;
}
- varDecl->SetCheckState(getCheckedState());
}
// Fill in default substitutions for the 'subtype' part of a type constraint decl
@@ -539,76 +795,55 @@ namespace Slang
}
}
- void SemanticsVisitor::CheckGenericConstraintDecl(GenericTypeConstraintDecl* decl)
+ void SemanticsDeclHeaderVisitor::visitGenericTypeConstraintDecl(GenericTypeConstraintDecl* decl)
{
// TODO: are there any other validations we can do at this point?
//
// There probably needs to be a kind of "occurs check" to make
// sure that the constraint actually applies to at least one
// of the parameters of the generic.
- if (decl->checkState == DeclCheckState::Unchecked)
- {
- decl->checkState = getCheckedState();
- CheckConstraintSubType(decl->sub);
- decl->sub = TranslateTypeNodeForced(decl->sub);
- decl->sup = TranslateTypeNodeForced(decl->sup);
- }
+ //
+ CheckConstraintSubType(decl->sub);
+ decl->sub = TranslateTypeNodeForced(decl->sub);
+ decl->sup = TranslateTypeNodeForced(decl->sup);
}
- void SemanticsVisitor::checkDecl(Decl* decl)
+ void SemanticsDeclHeaderVisitor::visitGenericTypeParamDecl(GenericTypeParamDecl* decl)
{
- EnsureDecl(decl, checkingPhase == CheckingPhase::Header ? DeclCheckState::CheckedHeader : DeclCheckState::Checked);
+ // TODO: could probably push checking the default value
+ // for a generic type parameter later.
+ //
+ decl->initType = CheckProperType(decl->initType);
}
- void SemanticsVisitor::checkGenericDeclHeader(GenericDecl* genericDecl)
+ void SemanticsDeclHeaderVisitor::visitGenericValueParamDecl(GenericValueParamDecl* decl)
{
- if (genericDecl->IsChecked(DeclCheckState::CheckedHeader))
- return;
- // check the parameters
+ checkVarDeclCommon(decl);
+ }
+
+ void SemanticsDeclHeaderVisitor::visitGenericDecl(GenericDecl* genericDecl)
+ {
+ genericDecl->SetCheckState(DeclCheckState::ReadyForLookup);
+
for (auto m : genericDecl->Members)
{
if (auto typeParam = as<GenericTypeParamDecl>(m))
{
- typeParam->initType = CheckProperType(typeParam->initType);
+ ensureDecl(typeParam, DeclCheckState::ReadyForReference);
}
else if (auto valParam = as<GenericValueParamDecl>(m))
{
- // TODO: some real checking here...
- CheckVarDeclCommon(valParam);
+ ensureDecl(valParam, DeclCheckState::ReadyForReference);
}
else if (auto constraint = as<GenericTypeConstraintDecl>(m))
{
- CheckGenericConstraintDecl(constraint);
+ ensureDecl(constraint, DeclCheckState::ReadyForReference);
}
}
-
- genericDecl->SetCheckState(DeclCheckState::CheckedHeader);
- }
-
- void SemanticsVisitor::visitGenericDecl(GenericDecl* genericDecl)
- {
- checkGenericDeclHeader(genericDecl);
-
- // check the nested declaration
- // TODO: this needs to be done in an appropriate environment...
- checkDecl(genericDecl->inner);
- genericDecl->SetCheckState(getCheckedState());
}
- void SemanticsVisitor::visitGenericTypeConstraintDecl(GenericTypeConstraintDecl * genericConstraintDecl)
+ void SemanticsDeclBasesVisitor::visitInheritanceDecl(InheritanceDecl* inheritanceDecl)
{
- if (genericConstraintDecl->IsChecked(DeclCheckState::CheckedHeader))
- return;
- // check the type being inherited from
- auto base = genericConstraintDecl->sup;
- base = TranslateTypeNode(base);
- genericConstraintDecl->sup = base;
- }
-
- void SemanticsVisitor::visitInheritanceDecl(InheritanceDecl* inheritanceDecl)
- {
- if (inheritanceDecl->IsChecked(DeclCheckState::CheckedHeader))
- return;
// check the type being inherited from
auto base = inheritanceDecl->base;
CheckConstraintSubType(base);
@@ -636,172 +871,182 @@ namespace Slang
getSink()->diagnose( base.exp, Diagnostics::expectedAnInterfaceGot, base.type);
}
- void SemanticsVisitor::visitSyntaxDecl(SyntaxDecl*)
- {
- // These are only used in the stdlib, so no checking is needed
- }
-
- void SemanticsVisitor::visitAttributeDecl(AttributeDecl*)
- {
- // These are only used in the stdlib, so no checking is needed
- }
-
- void SemanticsVisitor::visitGenericTypeParamDecl(GenericTypeParamDecl*)
+ // Concretize interface conformances so that we have witnesses as required for lookup.
+ // for lookup.
+ struct SemanticsDeclConformancesVisitor
+ : public SemanticsDeclVisitorBase
+ , public DeclVisitor<SemanticsDeclConformancesVisitor>
{
- // These are only used in the stdlib, so no checking is needed for now
- }
+ SemanticsDeclConformancesVisitor(SharedSemanticsContext* shared)
+ : SemanticsDeclVisitorBase(shared)
+ {}
- void SemanticsVisitor::visitGenericValueParamDecl(GenericValueParamDecl*)
- {
- // These are only used in the stdlib, so no checking is needed for now
- }
+ void visitDecl(Decl*) {}
+ void visitDeclGroup(DeclGroup*) {}
- void SemanticsVisitor::checkInterfaceConformancesRec(Decl* decl)
- {
// Any user-defined type may have declared interface conformances,
// which we should check.
//
- if( auto aggTypeDecl = as<AggTypeDecl>(decl) )
+ void visitAggTypeDecl(AggTypeDecl* aggTypeDecl)
{
checkAggTypeConformance(aggTypeDecl);
}
+
// Conformances can also come via `extension` declarations, and
// we should check them against the type(s) being extended.
//
- else if(auto extensionDecl = as<ExtensionDecl>(decl))
+ void visitExtensionDecl(ExtensionDecl* extensionDecl)
{
checkExtensionConformance(extensionDecl);
}
+ };
- // We need to handle the recursive cases here, the first
- // of which is a generic decl, where we want to recurivsely
- // check the inner declaration.
- //
- if(auto genericDecl = as<GenericDecl>(decl))
+ /// Recursively register any builtin declarations that need to be attached to the `session`.
+ ///
+ /// This function should only be needed for declarations in the standard library.
+ ///
+ static void _registerBuiltinDeclsRec(Session* session, Decl* decl)
+ {
+ if (auto builtinMod = decl->FindModifier<BuiltinTypeModifier>())
{
- checkInterfaceConformancesRec(genericDecl->inner);
+ registerBuiltinDecl(session, decl, builtinMod);
}
- // For any other kind of container declaration, we will
- // recurse into all of its member declarations, so that
- // we can handle, e.g., nested `struct` types.
- //
- else if(auto containerDecl = as<ContainerDecl>(decl))
+ if (auto magicMod = decl->FindModifier<MagicTypeModifier>())
{
- for(auto member : containerDecl->Members)
- {
- checkInterfaceConformancesRec(member);
- }
+ registerMagicDecl(session, decl, magicMod);
}
- }
- void SemanticsVisitor::visitModuleDecl(ModuleDecl* programNode)
- {
- // Try to register all the builtin decls
- for (auto decl : programNode->Members)
+ if(auto containerDecl = as<ContainerDecl>(decl))
{
- auto inner = decl;
- if (auto genericDecl = as<GenericDecl>(decl))
+ for(auto childDecl : containerDecl->Members)
{
- inner = genericDecl->inner;
- }
+ if(as<ScopeDecl>(childDecl))
+ continue;
- if (auto builtinMod = inner->FindModifier<BuiltinTypeModifier>())
- {
- registerBuiltinDecl(getSession(), decl, builtinMod);
- }
- if (auto magicMod = inner->FindModifier<MagicTypeModifier>())
- {
- registerMagicDecl(getSession(), decl, magicMod);
+ _registerBuiltinDeclsRec(session, childDecl);
}
}
-
- // We need/want to visit any `import` declarations before
- // anything else, to make sure that scoping works.
- for(auto& importDecl : programNode->getMembersOfType<ImportDecl>())
- {
- checkDecl(importDecl);
- }
- // register all extensions
- for (auto & s : programNode->getMembersOfType<ExtensionDecl>())
- registerExtension(s);
- for (auto & g : programNode->getMembersOfType<GenericDecl>())
+ if(auto genericDecl = as<GenericDecl>(decl))
{
- if (auto extDecl = as<ExtensionDecl>(g->inner))
- {
- checkGenericDeclHeader(g);
- registerExtension(extDecl);
- }
+ _registerBuiltinDeclsRec(session, genericDecl->inner);
}
- // check user defined attribute classes first
- for (auto decl : programNode->Members)
+ }
+
+ void SemanticsDeclVisitorBase::checkModule(ModuleDecl* moduleDecl)
+ {
+ // When we are dealing with code from the standard library,
+ // there is a potential problem where we might need to look
+ // up built-in types like `Int` through the session (e.g.,
+ // to determine the type for an integer literal), but those
+ // types might not have been registered yet. We solve that
+ // by doing a pre-process on standard-library code to find
+ // and register any built-in declarations.
+ //
+ // TODO: This could be factored into another visitor pass
+ // that fits the more standard checking below, but that would
+ // seemingly add overhead to checking things other than
+ // the standard library.
+ //
+ if(isFromStdLib(moduleDecl))
{
- if (auto typeMember = as<StructDecl>(decl))
- {
- bool isTypeAttributeClass = false;
- for (auto attrib : typeMember->GetModifiersOfType<UncheckedAttribute>())
- {
- if (attrib->name == getSession()->getNameObj("AttributeUsageAttribute"))
- {
- isTypeAttributeClass = true;
- break;
- }
- }
- if (isTypeAttributeClass)
- checkDecl(decl);
- }
+ _registerBuiltinDeclsRec(getSession(), moduleDecl);
}
- // check types
- for (auto & s : programNode->getMembersOfType<TypeDefDecl>())
- checkDecl(s.Ptr());
- for (int pass = 0; pass < 2; pass++)
+ // We need/want to visit any `import` declarations before
+ // anything else, to make sure that scoping works.
+ //
+ // TODO: This could be factored into another visitor pass
+ // that fits more with the standard checking below.
+ //
+ for(auto& importDecl : moduleDecl->getMembersOfType<ImportDecl>())
{
- checkingPhase = pass == 0 ? CheckingPhase::Header : CheckingPhase::Body;
-
- for (auto & s : programNode->getMembersOfType<AggTypeDecl>())
- {
- checkDecl(s.Ptr());
- }
- // HACK(tfoley): Visiting all generic declarations here,
- // because otherwise they won't get visited.
- for (auto & g : programNode->getMembersOfType<GenericDecl>())
- {
- checkDecl(g.Ptr());
- }
-
- // before checking conformance, make sure we check all the extension bodies
- // generic extension decls are already checked by the loop above
- for (auto & s : programNode->getMembersOfType<ExtensionDecl>())
- checkDecl(s);
-
- for (auto & func : programNode->getMembersOfType<FuncDecl>())
- {
- if (!func->IsChecked(getCheckedState()))
- {
- VisitFunctionDeclaration(func.Ptr());
- }
- }
- for (auto & func : programNode->getMembersOfType<FuncDecl>())
- {
- checkDecl(func);
- }
-
- if (getSink()->GetErrorCount() != 0)
- return;
-
- // Force everything to be fully checked, just in case
- // Note that we don't just call this on the program,
- // because we'd end up recursing into this very code path...
- for (auto d : programNode->Members)
- {
- EnusreAllDeclsRec(d);
- }
+ ensureDecl(importDecl, DeclCheckState::Checked);
+ }
- if (pass == 0)
- {
- checkInterfaceConformancesRec(programNode);
- }
+ // The entire goal of semantic checking is to get all of the
+ // declarations in the module up to `DeclCheckState::Checked`.
+ //
+ // The main catch is that checking one declaration A up to state M
+ // may required that declaration B is checked up to state N.
+ // A call to `ensureDecl(B, N)` can guarantee that things are checked
+ // when and where we need them, but that runs the risk of creating
+ // very deep recursion in the semantic checking.
+ //
+ // Instead, we would rather do more breadth-first checking,
+ // where everything gets checked up to state 1, 2, ...
+ // before anything gets too far ahead.
+ // We will therefore enumerate the states/phases for checking,
+ // and then iteratively try to update all declarations to each
+ // state in turn.
+ //
+ // Note: for a simpler language we could eliminate `ensureDecl`
+ // completely and *just* have these phases of checking.
+ // Unfortunately, we have some circularity between the phases:
+ //
+ // * Checking an overloaded call requires knowing the parameter
+ // types of all candidate callees.
+ //
+ // * Checking the parameter type of a function requires being
+ // able to check type expressions.
+ //
+ // * A type expression like `vector<T, N>` may have an arbitary
+ // expression for `N`.
+ //
+ // * An arbitrary expression may include function calls, which
+ // may be to overloaded functions.
+ //
+ // Languages like C++ solve the apparent problem by making
+ // restrictions on order of declaration/definition (and by
+ // requiring forward declarations or the `template`/`typename`
+ // keywrods in some cases).
+ //
+ // TODO: We could eventually eliminate the potential recursion
+ // in checking by splitting each phase into a "requirements gathering"
+ // step and an actual execution step.
+ //
+ // When checking a declaration D up to state S, the requirements
+ // gathering step would produce a list of pairs `(someDecl, someState)`
+ // indicating that `someDecl` must be in `someState` before the
+ // actual execution of checking for `(D,S)` can proceeed. The checker
+ // can then produce an elaborated dependency graph and select nodes
+ // for execution in an order that satisfies all the dependencies.
+ //
+ // Such a more elaborate checking scheme will have to wait for another
+ // day, but might be worth it (or even necessary) if/when we want to
+ // support incremental compilation.
+ //
+ DeclCheckState states[] =
+ {
+ DeclCheckState::ModifiersChecked,
+ DeclCheckState::ReadyForReference,
+ DeclCheckState::ReadyForLookup,
+ DeclCheckState::ReadyForLookup,
+ DeclCheckState::Checked
+ };
+ for(auto s : states)
+ {
+ // When advancing to state `s` we will recursively
+ // advance all declarations rooted in the module
+ // up to `s`.
+ //
+ // TODO: In cases where a large module is split across files,
+ // we could potentially parallelize front-end compilation by
+ // having multiple instances of the front end where each is
+ // only responsible for those declarations in a given file.
+ //
+ // Under that model, we might only apply later phases of
+ // checking (notably the final push to `DeclState::Checked`)
+ // to the subset of declarations coming from a given source
+ // file.
+ //
+ _ensureAllDeclsRec(this, moduleDecl, s);
}
+
+ // Once we have completed the above loop, all declarations not
+ // nested in function bodies should be in `DeclState::Checked`.
+ // Furthermore, because a fully checked function will have checked
+ // its body, this also means that all function bodies and the
+ // declarations they contain should be fully checked.
}
bool SemanticsVisitor::doesSignatureMatchRequirement(
@@ -1004,7 +1249,7 @@ namespace Slang
{
if(auto requiredTypeDeclRef = requiredMemberDeclRef.as<AssocTypeDecl>())
{
- checkDecl(subAggTypeDeclRef.getDecl());
+ ensureDecl(subAggTypeDeclRef, DeclCheckState::CanUseAsType);
auto satisfyingType = DeclRefType::Create(getSession(), subAggTypeDeclRef);
return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable);
@@ -1016,7 +1261,7 @@ namespace Slang
// check if the specified type satisfies the constraints defined by the associated type
if (auto requiredTypeDeclRef = requiredMemberDeclRef.as<AssocTypeDecl>())
{
- checkDecl(typedefDeclRef.getDecl());
+ ensureDecl(typedefDeclRef, DeclCheckState::CanUseAsType);
auto satisfyingType = getNamedType(getSession(), typedefDeclRef);
return doesTypeSatisfyAssociatedTypeRequirement(satisfyingType, requiredTypeDeclRef, witnessTable);
@@ -1155,7 +1400,8 @@ namespace Slang
// We need to check the declaration of the interface
// before we can check that we conform to it.
- checkDecl(interfaceDeclRef.getDecl());
+ //
+ ensureDecl(interfaceDeclRef, DeclCheckState::CanReadInterfaceRequirements);
// We will construct the witness table, and register it
// *before* we go about checking fine-grained requirements,
@@ -1372,37 +1618,16 @@ namespace Slang
}
}
- void SemanticsVisitor::visitAggTypeDecl(AggTypeDecl* decl)
+ void SemanticsDeclBasesVisitor::visitAggTypeDecl(AggTypeDecl* decl)
{
- if (decl->IsChecked(getCheckedState()))
- return;
-
- // TODO: we should check inheritance declarations
- // first, since they need to be validated before
- // we can make use of the type (e.g., you need
- // to know that `A` inherits from `B` in order
- // to check an expression like `aValue.bMethod()`
- // where `aValue` is of type `A` but `bMethod`
- // is defined in type `B`.
- //
- // TODO: We should also add a pass that takes
- // all the stated inheritance relationships,
- // expands them to include implicit inheritance,
- // and then linearizes them. This would allow
- // later passes that need to know everything
- // a type inherits from to proceed linearly
- // through the list, rather than having to
- // recurse (and potentially see the same interface
- // more than once).
-
- decl->SetCheckState(DeclCheckState::CheckedHeader);
+ // TODO: We need to enumerate the bases here,
+ // and ideally form a "class precedence list"
+ // from them.
- // Now check all of the member declarations.
- for (auto member : decl->Members)
+ for( auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>() )
{
- checkDecl(member);
+ ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl);
}
- decl->SetCheckState(getCheckedState());
}
bool SemanticsVisitor::isIntegerBaseType(BaseType baseType)
@@ -1439,357 +1664,286 @@ namespace Slang
getSink()->diagnose(loc, Diagnostics::invalidEnumTagType, type);
}
- void SemanticsVisitor::visitEnumDecl(EnumDecl* decl)
+ void SemanticsDeclBasesVisitor::visitEnumDecl(EnumDecl* decl)
{
- if (decl->IsChecked(getCheckedState()))
- return;
-
- // We need to be careful to avoid recursion in the
- // type-checking logic. We will do the minimal work
- // to make the type usable in the first phase, and
- // then check the actual cases in the second phase.
- //
- if(this->checkingPhase == CheckingPhase::Header)
- {
- // Look at inheritance clauses, and
- // see if one of them is making the enum
- // "inherit" from a concrete type.
- // This will become the "tag" type
- // of the enum.
- RefPtr<Type> tagType;
- InheritanceDecl* tagTypeInheritanceDecl = nullptr;
- for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
- {
- checkDecl(inheritanceDecl);
+ // Look at inheritance clauses, and
+ // see if one of them is making the enum
+ // "inherit" from a concrete type.
+ // This will become the "tag" type
+ // of the enum.
+ RefPtr<Type> tagType;
+ InheritanceDecl* tagTypeInheritanceDecl = nullptr;
+ for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>())
+ {
+ ensureDecl(inheritanceDecl, DeclCheckState::CanUseBaseOfInheritanceDecl);
- // Look at the type being inherited from.
- auto superType = inheritanceDecl->base.type;
+ // Look at the type being inherited from.
+ auto superType = inheritanceDecl->base.type;
- if(auto errorType = as<ErrorType>(superType))
+ if(auto errorType = as<ErrorType>(superType))
+ {
+ // Ignore any erroneous inheritance clauses.
+ continue;
+ }
+ else if(auto declRefType = as<DeclRefType>(superType))
+ {
+ if(auto interfaceDeclRef = declRefType->declRef.as<InterfaceDecl>())
{
- // Ignore any erroneous inheritance clauses.
+ // Don't consider interface bases as candidates for
+ // the tag type.
continue;
}
- else if(auto declRefType = as<DeclRefType>(superType))
- {
- if(auto interfaceDeclRef = declRefType->declRef.as<InterfaceDecl>())
- {
- // Don't consider interface bases as candidates for
- // the tag type.
- continue;
- }
- }
-
- if(tagType)
- {
- // We already found a tag type.
- getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType);
- getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType);
- break;
- }
- else
- {
- tagType = superType;
- tagTypeInheritanceDecl = inheritanceDecl;
- }
}
- // If a tag type has not been set, then we
- // default it to the built-in `int` type.
- //
- // TODO: In the far-flung future we may want to distinguish
- // `enum` types that have a "raw representation" like this from
- // ones that are purely abstract and don't expose their
- // type of their tag.
- if(!tagType)
+ if(tagType)
{
- tagType = getSession()->getIntType();
+ // We already found a tag type.
+ getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType);
+ getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType);
+ break;
}
else
{
- // TODO: Need to establish that the tag
- // type is suitable. (e.g., if we are going
- // to allow raw values for case tags to be
- // derived automatically, then the tag
- // type needs to be some kind of integer type...)
- //
- // For now we will just be harsh and require it
- // to be one of a few builtin types.
- validateEnumTagType(tagType, tagTypeInheritanceDecl->loc);
+ tagType = superType;
+ tagTypeInheritanceDecl = inheritanceDecl;
}
- decl->tagType = tagType;
+ }
+ // If a tag type has not been set, then we
+ // default it to the built-in `int` type.
+ //
+ // TODO: In the far-flung future we may want to distinguish
+ // `enum` types that have a "raw representation" like this from
+ // ones that are purely abstract and don't expose their
+ // type of their tag.
+ if(!tagType)
+ {
+ tagType = getSession()->getIntType();
+ }
+ else
+ {
+ // TODO: Need to establish that the tag
+ // type is suitable. (e.g., if we are going
+ // to allow raw values for case tags to be
+ // derived automatically, then the tag
+ // type needs to be some kind of integer type...)
+ //
+ // For now we will just be harsh and require it
+ // to be one of a few builtin types.
+ validateEnumTagType(tagType, tagTypeInheritanceDecl->loc);
+ }
+ decl->tagType = tagType;
- // An `enum` type should automatically conform to the `__EnumType` interface.
- // The compiler needs to insert this conformance behind the scenes, and this
- // seems like the best place to do it.
- {
- // First, look up the type of the `__EnumType` interface.
- RefPtr<Type> enumTypeType = getSession()->getEnumTypeType();
-
- RefPtr<InheritanceDecl> enumConformanceDecl = new InheritanceDecl();
- enumConformanceDecl->ParentDecl = decl;
- enumConformanceDecl->loc = decl->loc;
- enumConformanceDecl->base.type = getSession()->getEnumTypeType();
- decl->Members.add(enumConformanceDecl);
-
- // The `__EnumType` interface has one required member, the `__Tag` type.
- // We need to satisfy this requirement automatically, rather than require
- // the user to actually declare a member with this name (otherwise we wouldn't
- // let them define a tag value with the name `__Tag`).
- //
- RefPtr<WitnessTable> witnessTable = new WitnessTable();
- enumConformanceDecl->witnessTable = witnessTable;
- Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag");
- Decl* tagAssociatedTypeDecl = nullptr;
- if(auto enumTypeTypeDeclRefType = enumTypeType.dynamicCast<DeclRefType>())
+ // An `enum` type should automatically conform to the `__EnumType` interface.
+ // The compiler needs to insert this conformance behind the scenes, and this
+ // seems like the best place to do it.
+ {
+ // First, look up the type of the `__EnumType` interface.
+ RefPtr<Type> enumTypeType = getSession()->getEnumTypeType();
+
+ RefPtr<InheritanceDecl> enumConformanceDecl = new InheritanceDecl();
+ enumConformanceDecl->ParentDecl = decl;
+ enumConformanceDecl->loc = decl->loc;
+ enumConformanceDecl->base.type = getSession()->getEnumTypeType();
+ decl->Members.add(enumConformanceDecl);
+
+ // The `__EnumType` interface has one required member, the `__Tag` type.
+ // We need to satisfy this requirement automatically, rather than require
+ // the user to actually declare a member with this name (otherwise we wouldn't
+ // let them define a tag value with the name `__Tag`).
+ //
+ RefPtr<WitnessTable> witnessTable = new WitnessTable();
+ enumConformanceDecl->witnessTable = witnessTable;
+
+ Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag");
+ Decl* tagAssociatedTypeDecl = nullptr;
+ if(auto enumTypeTypeDeclRefType = enumTypeType.dynamicCast<DeclRefType>())
+ {
+ if(auto enumTypeTypeInterfaceDecl = as<InterfaceDecl>(enumTypeTypeDeclRefType->declRef.getDecl()))
{
- if(auto enumTypeTypeInterfaceDecl = as<InterfaceDecl>(enumTypeTypeDeclRefType->declRef.getDecl()))
+ for(auto memberDecl : enumTypeTypeInterfaceDecl->Members)
{
- for(auto memberDecl : enumTypeTypeInterfaceDecl->Members)
+ if(memberDecl->getName() == tagAssociatedTypeName)
{
- if(memberDecl->getName() == tagAssociatedTypeName)
- {
- tagAssociatedTypeDecl = memberDecl;
- break;
- }
+ tagAssociatedTypeDecl = memberDecl;
+ break;
}
}
}
- if(!tagAssociatedTypeDecl)
- {
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), decl, "failed to find built-in declaration '__Tag'");
- }
+ }
+ if(!tagAssociatedTypeDecl)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), decl, "failed to find built-in declaration '__Tag'");
+ }
- // Okay, add the conformance witness for `__Tag` being satisfied by `tagType`
- witnessTable->requirementDictionary.Add(tagAssociatedTypeDecl, RequirementWitness(tagType));
+ // Okay, add the conformance witness for `__Tag` being satisfied by `tagType`
+ witnessTable->requirementDictionary.Add(tagAssociatedTypeDecl, RequirementWitness(tagType));
- // TODO: we actually also need to synthesize a witness for the conformance of `tagType`
- // to the `__BuiltinIntegerType` interface, because that is a constraint on the
- // associated type `__Tag`.
+ // TODO: we actually also need to synthesize a witness for the conformance of `tagType`
+ // to the `__BuiltinIntegerType` interface, because that is a constraint on the
+ // associated type `__Tag`.
- // TODO: eventually we should consider synthesizing other requirements for
- // the min/max tag values, or the total number of tags, so that people don't
- // have to declare these as additional cases.
+ // TODO: eventually we should consider synthesizing other requirements for
+ // the min/max tag values, or the total number of tags, so that people don't
+ // have to declare these as additional cases.
- enumConformanceDecl->SetCheckState(DeclCheckState::Checked);
- }
+ enumConformanceDecl->SetCheckState(DeclCheckState::Checked);
}
- else if( checkingPhase == CheckingPhase::Body )
- {
- auto enumType = DeclRefType::Create(
- getSession(),
- makeDeclRef(decl));
+ }
- auto tagType = decl->tagType;
+ void SemanticsDeclBodyVisitor::visitEnumDecl(EnumDecl* decl)
+ {
+ auto enumType = DeclRefType::Create(
+ getSession(),
+ makeDeclRef(decl));
- // Check the enum cases in order.
- for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>())
- {
- // Each case defines a value of the enum's type.
- //
- // TODO: If we ever support enum cases with payloads,
- // then they would probably have a type that is a
- // `FunctionType` from the payload types to the
- // enum type.
- //
- caseDecl->type.type = enumType;
+ auto tagType = decl->tagType;
- checkDecl(caseDecl);
- }
+ // Check the enum cases in order.
+ for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>())
+ {
+ // Each case defines a value of the enum's type.
+ //
+ // TODO: If we ever support enum cases with payloads,
+ // then they would probably have a type that is a
+ // `FunctionType` from the payload types to the
+ // enum type.
+ //
+ // TODO(tfoley): the case should grab its type when
+ // doing its own header checking, rather than rely on this...
+ caseDecl->type.type = enumType;
+
+ ensureDecl(caseDecl, DeclCheckState::Checked);
+ }
- // For any enum case that didn't provide an explicit
- // tag value, derived an appropriate tag value.
- IntegerLiteralValue defaultTag = 0;
- for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>())
+ // For any enum case that didn't provide an explicit
+ // tag value, derived an appropriate tag value.
+ IntegerLiteralValue defaultTag = 0;
+ for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>())
+ {
+ if(auto explicitTagValExpr = caseDecl->tagExpr)
{
- if(auto explicitTagValExpr = caseDecl->tagExpr)
- {
- // This tag has an initializer, so it should establish
- // the tag value for a successor case that doesn't
- // provide an explicit tag.
+ // This tag has an initializer, so it should establish
+ // the tag value for a successor case that doesn't
+ // provide an explicit tag.
- RefPtr<IntVal> explicitTagVal = TryConstantFoldExpr(explicitTagValExpr);
- if(explicitTagVal)
+ RefPtr<IntVal> explicitTagVal = TryConstantFoldExpr(explicitTagValExpr);
+ if(explicitTagVal)
+ {
+ if(auto constIntVal = as<ConstantIntVal>(explicitTagVal))
{
- if(auto constIntVal = as<ConstantIntVal>(explicitTagVal))
- {
- defaultTag = constIntVal->value;
- }
- else
- {
- // TODO: need to handle other possibilities here
- getSink()->diagnose(explicitTagValExpr, Diagnostics::unexpectedEnumTagExpr);
- }
+ defaultTag = constIntVal->value;
}
else
{
- // If this happens, then the explicit tag value expression
- // doesn't seem to be a constant after all. In this case
- // we expect the checking logic to have applied already.
+ // TODO: need to handle other possibilities here
+ getSink()->diagnose(explicitTagValExpr, Diagnostics::unexpectedEnumTagExpr);
}
}
else
{
- // This tag has no initializer, so it should use
- // the default tag value we are tracking.
- RefPtr<IntegerLiteralExpr> tagValExpr = new IntegerLiteralExpr();
- tagValExpr->loc = caseDecl->loc;
- tagValExpr->type = QualType(tagType);
- tagValExpr->value = defaultTag;
-
- caseDecl->tagExpr = tagValExpr;
+ // If this happens, then the explicit tag value expression
+ // doesn't seem to be a constant after all. In this case
+ // we expect the checking logic to have applied already.
}
-
- // Default tag for the next case will be one more than
- // for the most recent case.
- //
- // TODO: We might consider adding a `[flags]` attribute
- // that modifies this behavior to be `defaultTagForCase <<= 1`.
- //
- defaultTag++;
}
-
- // Now check any other member declarations.
- for(auto memberDecl : decl->Members)
+ else
{
- // Already checked inheritance declarations above.
- if(auto inheritanceDecl = as<InheritanceDecl>(memberDecl))
- continue;
-
- // Already checked enum case declarations above.
- if(auto caseDecl = as<EnumCaseDecl>(memberDecl))
- continue;
+ // This tag has no initializer, so it should use
+ // the default tag value we are tracking.
+ RefPtr<IntegerLiteralExpr> tagValExpr = new IntegerLiteralExpr();
+ tagValExpr->loc = caseDecl->loc;
+ tagValExpr->type = QualType(tagType);
+ tagValExpr->value = defaultTag;
- // TODO: Right now we don't support other kinds of
- // member declarations on an `enum`, but that is
- // something we may want to allow in the long run.
- //
- checkDecl(memberDecl);
+ caseDecl->tagExpr = tagValExpr;
}
+
+ // Default tag for the next case will be one more than
+ // for the most recent case.
+ //
+ // TODO: We might consider adding a `[flags]` attribute
+ // that modifies this behavior to be `defaultTagForCase <<= 1`.
+ //
+ defaultTag++;
}
- decl->SetCheckState(getCheckedState());
}
- void SemanticsVisitor::visitEnumCaseDecl(EnumCaseDecl* decl)
+ void SemanticsDeclBodyVisitor::visitEnumCaseDecl(EnumCaseDecl* decl)
{
- if (decl->IsChecked(getCheckedState()))
- return;
-
- if(checkingPhase == CheckingPhase::Body)
- {
- // An enum case had better appear inside an enum!
- //
- // TODO: Do we need/want to support generic cases some day?
- auto parentEnumDecl = as<EnumDecl>(decl->ParentDecl);
- SLANG_ASSERT(parentEnumDecl);
-
- // The tag type should have already been set by
- // the surrounding `enum` declaration.
- auto tagType = parentEnumDecl->tagType;
- SLANG_ASSERT(tagType);
-
- // Need to check the init expression, if present, since
- // that represents the explicit tag for this case.
- if(auto initExpr = decl->tagExpr)
- {
- initExpr = CheckExpr(initExpr);
- initExpr = coerce(tagType, initExpr);
+ // An enum case had better appear inside an enum!
+ //
+ // TODO: Do we need/want to support generic cases some day?
+ auto parentEnumDecl = as<EnumDecl>(decl->ParentDecl);
+ SLANG_ASSERT(parentEnumDecl);
- // We want to enforce that this is an integer constant
- // expression, but we don't actually care to retain
- // the value.
- CheckIntegerConstantExpression(initExpr);
+ // The tag type should have already been set by
+ // the surrounding `enum` declaration.
+ auto tagType = parentEnumDecl->tagType;
+ SLANG_ASSERT(tagType);
- decl->tagExpr = initExpr;
- }
- }
+ // Need to check the init expression, if present, since
+ // that represents the explicit tag for this case.
+ if(auto initExpr = decl->tagExpr)
+ {
+ initExpr = CheckExpr(initExpr);
+ initExpr = coerce(tagType, initExpr);
- decl->SetCheckState(getCheckedState());
- }
+ // We want to enforce that this is an integer constant
+ // expression, but we don't actually care to retain
+ // the value.
+ CheckIntegerConstantExpression(initExpr);
- void SemanticsVisitor::visitDeclGroup(DeclGroup* declGroup)
- {
- for (auto decl : declGroup->decls)
- {
- dispatchDecl(decl);
+ decl->tagExpr = initExpr;
}
}
- void SemanticsVisitor::visitTypeDefDecl(TypeDefDecl* decl)
+ void SemanticsVisitor::ensureDeclBase(DeclBase* declBase, DeclCheckState state)
{
- if (decl->IsChecked(getCheckedState())) return;
- if (checkingPhase == CheckingPhase::Header)
+ if(auto decl = as<Decl>(declBase))
{
- decl->type = CheckProperType(decl->type);
+ ensureDecl(decl, state);
}
- decl->SetCheckState(getCheckedState());
- }
-
- void SemanticsVisitor::visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl)
- {
- if (decl->IsChecked(getCheckedState())) return;
- if (checkingPhase == CheckingPhase::Header)
+ else if(auto declGroup = as<DeclGroup>(declBase))
{
- decl->SetCheckState(DeclCheckState::CheckedHeader);
- // global generic param only allowed in global scope
- auto program = as<ModuleDecl>(decl->ParentDecl);
- if (!program)
- getSink()->diagnose(decl, Slang::Diagnostics::globalGenParamInGlobalScopeOnly);
- // Now check all of the member declarations.
- for (auto member : decl->Members)
+ for(auto dd : declGroup->decls)
{
- checkDecl(member);
+ ensureDecl(dd, state);
}
}
- decl->SetCheckState(getCheckedState());
+ else
+ {
+ SLANG_UNEXPECTED("unknown case for declaration");
+ }
}
- void SemanticsVisitor::visitAssocTypeDecl(AssocTypeDecl* decl)
+ void SemanticsDeclHeaderVisitor::visitTypeDefDecl(TypeDefDecl* decl)
{
- if (decl->IsChecked(getCheckedState())) return;
- if (checkingPhase == CheckingPhase::Header)
- {
- decl->SetCheckState(DeclCheckState::CheckedHeader);
-
- // assoctype only allowed in an interface
- auto interfaceDecl = as<InterfaceDecl>(decl->ParentDecl);
- if (!interfaceDecl)
- getSink()->diagnose(decl, Slang::Diagnostics::assocTypeInInterfaceOnly);
-
- // Now check all of the member declarations.
- for (auto member : decl->Members)
- {
- checkDecl(member);
- }
- }
- decl->SetCheckState(getCheckedState());
+ decl->type = CheckProperType(decl->type);
}
- void SemanticsVisitor::visitFuncDecl(FuncDecl* functionNode)
+ void SemanticsDeclHeaderVisitor::visitGlobalGenericParamDecl(GlobalGenericParamDecl* decl)
{
- if (functionNode->IsChecked(getCheckedState()))
- return;
+ // global generic param only allowed in global scope
+ auto program = as<ModuleDecl>(decl->ParentDecl);
+ if (!program)
+ getSink()->diagnose(decl, Slang::Diagnostics::globalGenParamInGlobalScopeOnly);
+ }
- if (checkingPhase == CheckingPhase::Header)
- {
- VisitFunctionDeclaration(functionNode);
- }
- // TODO: This should really only set "checked header"
- functionNode->SetCheckState(getCheckedState());
+ void SemanticsDeclHeaderVisitor::visitAssocTypeDecl(AssocTypeDecl* decl)
+ {
+ // assoctype only allowed in an interface
+ auto interfaceDecl = as<InterfaceDecl>(decl->ParentDecl);
+ if (!interfaceDecl)
+ getSink()->diagnose(decl, Slang::Diagnostics::assocTypeInInterfaceOnly);
+ }
- if (checkingPhase == CheckingPhase::Body)
+ void SemanticsDeclBodyVisitor::visitFuncDecl(FuncDecl* funcDecl)
+ {
+ if (auto body = funcDecl->Body)
{
- // TODO: should put the checking of the body onto a "work list"
- // to avoid recursion here.
- if (functionNode->Body)
- {
- auto oldFunc = function;
- this->function = functionNode;
- checkStmt(functionNode->Body);
- this->function = oldFunc;
- }
+ checkBodyStmt(body, funcDecl);
}
}
@@ -2189,12 +2343,7 @@ namespace Slang
}
}
- void SemanticsVisitor::visitScopeDecl(ScopeDecl*)
- {
- // Nothing to do
- }
-
- void SemanticsVisitor::visitParamDecl(ParamDecl* paramDecl)
+ void SemanticsDeclHeaderVisitor::visitParamDecl(ParamDecl* paramDecl)
{
// TODO: This logic should be shared with the other cases of
// variable declarations. The main reason I am not doing it
@@ -2208,8 +2357,11 @@ namespace Slang
typeExpr = CheckUsableType(typeExpr);
paramDecl->type = typeExpr;
}
+ }
- paramDecl->SetCheckState(DeclCheckState::CheckedHeader);
+ void SemanticsDeclBodyVisitor::visitParamDecl(ParamDecl* paramDecl)
+ {
+ auto typeExpr = paramDecl->type;
// The "initializer" expression for a parameter represents
// a default argument value to use if an explicit one is
@@ -2245,33 +2397,26 @@ namespace Slang
getSink()->diagnose(initExpr, Diagnostics::outputParameterCannotHaveDefaultValue);
}
}
-
- paramDecl->SetCheckState(DeclCheckState::Checked);
}
- void SemanticsVisitor::VisitFunctionDeclaration(FuncDecl *functionNode)
+ void SemanticsDeclHeaderVisitor::visitFuncDecl(FuncDecl* funcDecl)
{
- if (functionNode->IsChecked(DeclCheckState::CheckedHeader)) return;
- functionNode->SetCheckState(DeclCheckState::CheckingHeader);
- auto oldFunc = this->function;
- this->function = functionNode;
-
- auto resultType = functionNode->ReturnType;
+ auto resultType = funcDecl->ReturnType;
if(resultType.exp)
{
- resultType = CheckProperType(functionNode->ReturnType);
+ resultType = CheckProperType(resultType);
}
else
{
resultType = TypeExp(getSession()->getVoidType());
}
- functionNode->ReturnType = resultType;
+ funcDecl->ReturnType = resultType;
HashSet<Name*> paraNames;
- for (auto & para : functionNode->GetParameters())
+ for (auto & para : funcDecl->GetParameters())
{
- EnsureDecl(para, DeclCheckState::CheckedHeader);
+ ensureDecl(para, DeclCheckState::ReadyForReference);
if (paraNames.Contains(para->getName()))
{
@@ -2280,11 +2425,9 @@ namespace Slang
else
paraNames.Add(para->getName());
}
- this->function = oldFunc;
- functionNode->SetCheckState(DeclCheckState::CheckedHeader);
// One last bit of validation: check if we are redeclaring an existing function
- ValidateFunctionRedeclaration(functionNode);
+ ValidateFunctionRedeclaration(funcDecl);
}
IntegerLiteralValue SemanticsVisitor::GetMinBound(RefPtr<IntVal> val)
@@ -2353,21 +2496,9 @@ namespace Slang
}
}
- void SemanticsVisitor::visitVarDecl(VarDecl* varDecl)
- {
- CheckVarDeclCommon(varDecl);
- }
-
- void SemanticsVisitor::registerExtension(ExtensionDecl* decl)
+ void SemanticsDeclBasesVisitor::visitExtensionDecl(ExtensionDecl* decl)
{
- if (decl->IsChecked(DeclCheckState::CheckedHeader))
- return;
-
- decl->SetCheckState(DeclCheckState::CheckingHeader);
decl->targetType = CheckProperType(decl->targetType);
- decl->SetCheckState(DeclCheckState::CheckedHeader);
-
- // TODO: need to check that the target type names a declaration...
if (auto targetDeclRefType = as<DeclRefType>(decl->targetType))
{
@@ -2383,22 +2514,6 @@ namespace Slang
getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here");
}
- void SemanticsVisitor::visitExtensionDecl(ExtensionDecl* decl)
- {
- if (decl->IsChecked(getCheckedState())) return;
-
- if (!as<DeclRefType>(decl->targetType))
- {
- getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here");
- }
- // now check the members of the extension
- for (auto m : decl->Members)
- {
- checkDecl(m);
- }
- decl->SetCheckState(getCheckedState());
- }
-
RefPtr<Type> SemanticsVisitor::findResultTypeForConstructorDecl(ConstructorDecl* decl)
{
// We want to look at the parent of the declaration,
@@ -2436,35 +2551,23 @@ namespace Slang
}
}
- void SemanticsVisitor::visitConstructorDecl(ConstructorDecl* decl)
+ void SemanticsDeclHeaderVisitor::visitConstructorDecl(ConstructorDecl* decl)
{
- if (decl->IsChecked(getCheckedState())) return;
- if (checkingPhase == CheckingPhase::Header)
- {
- decl->SetCheckState(DeclCheckState::CheckingHeader);
-
- for (auto& paramDecl : decl->GetParameters())
- {
- paramDecl->type = CheckUsableType(paramDecl->type);
- }
-
- // We need to compute the result tyep for this declaration,
- // since it wasn't filled in for us.
- decl->ReturnType.type = findResultTypeForConstructorDecl(decl);
- }
- else
+ for (auto& paramDecl : decl->GetParameters())
{
- // TODO(tfoley): check body
+ ensureDecl(paramDecl, DeclCheckState::CanUseTypeOfValueDecl);
}
- decl->SetCheckState(getCheckedState());
+
+ // We need to compute the result tyep for this declaration,
+ // since it wasn't filled in for us.
+ decl->ReturnType.type = findResultTypeForConstructorDecl(decl);
}
- void SemanticsVisitor::visitSubscriptDecl(SubscriptDecl* decl)
+ void SemanticsDeclHeaderVisitor::visitSubscriptDecl(SubscriptDecl* decl)
{
- if (decl->IsChecked(getCheckedState())) return;
for (auto& paramDecl : decl->GetParameters())
{
- paramDecl->type = CheckUsableType(paramDecl->type);
+ ensureDecl(paramDecl, DeclCheckState::CanUseTypeOfValueDecl);
}
decl->ReturnType = CheckUsableType(decl->ReturnType);
@@ -2494,40 +2597,25 @@ namespace Slang
getterDecl->ParentDecl = decl;
decl->Members.add(getterDecl);
}
-
- for(auto mm : decl->Members)
- {
- checkDecl(mm);
- }
-
- decl->SetCheckState(getCheckedState());
}
- void SemanticsVisitor::visitAccessorDecl(AccessorDecl* decl)
+ void SemanticsDeclHeaderVisitor::visitAccessorDecl(AccessorDecl* decl)
{
- if (checkingPhase == CheckingPhase::Header)
+ // An accessor must appear nested inside a subscript declaration (today),
+ // or a property declaration (when we add them). It will derive
+ // its return type from the outer declaration, so we handle both
+ // of these checks at the same place.
+ auto parent = decl->ParentDecl;
+ if (auto parentSubscript = as<SubscriptDecl>(parent))
{
- // An accessor must appear nested inside a subscript declaration (today),
- // or a property declaration (when we add them). It will derive
- // its return type from the outer declaration, so we handle both
- // of these checks at the same place.
- auto parent = decl->ParentDecl;
- if (auto parentSubscript = as<SubscriptDecl>(parent))
- {
- decl->ReturnType = parentSubscript->ReturnType;
- }
- // TODO: when we add "property" declarations, check for them here
- else
- {
- getSink()->diagnose(decl, Diagnostics::accessorMustBeInsideSubscriptOrProperty);
- }
-
+ ensureDecl(parentSubscript, DeclCheckState::CanUseTypeOfValueDecl);
+ decl->ReturnType = parentSubscript->ReturnType;
}
+ // TODO: when we add "property" declarations, check for them here
else
{
- // TODO: check the body!
+ getSink()->diagnose(decl, Diagnostics::accessorMustBeInsideSubscriptOrProperty);
}
- decl->SetCheckState(getCheckedState());
}
GenericDecl* SemanticsVisitor::GetOuterGeneric(Decl* decl)
@@ -2655,6 +2743,7 @@ namespace Slang
QualType SemanticsVisitor::GetTypeForDeclRef(DeclRef<Decl> declRef)
{
+ RefPtr<Type> typeResult;
return getTypeForDeclRef(
getSession(),
this,
@@ -2667,6 +2756,7 @@ namespace Slang
{
// If we've imported this one already, then
// skip the step where we modify the current scope.
+ auto& importedModules = getShared()->importedModules;
if (importedModules.Contains(moduleDecl))
{
return;
@@ -2693,16 +2783,8 @@ namespace Slang
}
}
- void SemanticsVisitor::visitEmptyDecl(EmptyDecl* /*decl*/)
- {
- // nothing to do
- }
-
- void SemanticsVisitor::visitImportDecl(ImportDecl* decl)
+ void SemanticsDeclHeaderVisitor::visitImportDecl(ImportDecl* decl)
{
- if(decl->IsChecked(DeclCheckState::CheckedHeader))
- return;
-
// We need to look for a module with the specified name
// (whether it has already been loaded, or needs to
// be loaded), and then put its declarations into
@@ -2738,8 +2820,32 @@ namespace Slang
{
module->addModuleDependency(importedModule);
}
+ }
+
+ static void _dispatchDeclCheckingVisitor(Decl* decl, DeclCheckState state, SharedSemanticsContext* shared)
+ {
+ switch(state)
+ {
+ case DeclCheckState::ModifiersChecked:
+ SemanticsDeclModifiersVisitor(shared).dispatch(decl);
+ break;
+
+ case DeclCheckState::ReadyForReference:
+ SemanticsDeclHeaderVisitor(shared).dispatch(decl);
+ break;
- decl->SetCheckState(getCheckedState());
+ case DeclCheckState::ReadyForLookup:
+ SemanticsDeclBasesVisitor(shared).dispatch(decl);
+ break;
+
+ case DeclCheckState::ReadyForConformances:
+ SemanticsDeclConformancesVisitor(shared).dispatch(decl);
+ break;
+
+ case DeclCheckState::Checked:
+ SemanticsDeclBodyVisitor(shared).dispatch(decl);
+ break;
+ }
}
}