diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-10-25 14:19:56 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-10-25 14:19:56 -0700 |
| commit | c886ca811975e91cedca898a561ff65a5663272d (patch) | |
| tree | 43dbae0f34972f293144dde9edaadef413462508 /source/slang/slang-check-expr.cpp | |
| parent | 7cf9b65c3836cdc17e6761bfd76383564ff0ec9d (diff) | |
Refactor semantic checking code into more files (#1097)
The semantic checking logic was all inside `slang-check.cpp` and as a result this was a monster file that was extremely hard to follow. This change splits `slang-check.cpp` into several smaller files, although some of the resulting files are still quite large.
This change attempts to be a copy-paste job as much as possible and does *not* perform any cleanup on naming, structure, duplication, etc. in the code it deal with. No function bodies or signatures have been touched.
Diffstat (limited to 'source/slang/slang-check-expr.cpp')
| -rw-r--r-- | source/slang/slang-check-expr.cpp | 1598 |
1 files changed, 1598 insertions, 0 deletions
diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp new file mode 100644 index 000000000..b9400f34a --- /dev/null +++ b/source/slang/slang-check-expr.cpp @@ -0,0 +1,1598 @@ +// slang-check-expr.cpp +#include "slang-check-impl.h" + +// This file contains semantic-checking logic for the various +// expression types in the AST. +// +// Note that some cases of expression checking are split +// of into their own files. Notably: +// +// * `slang-check-overload.cpp` is responsible for the logic of resolving overloaded calls +// +// * `slang-check-conversion.cpp` is responsible for the logic of handling type conversion/coercion + +#include "slang-lookup.h" + +namespace Slang +{ + RefPtr<DeclRefType> SemanticsVisitor::getExprDeclRefType(Expr * expr) + { + if (auto typetype = as<TypeType>(expr->type)) + return typetype->type.dynamicCast<DeclRefType>(); + else + return as<DeclRefType>(expr->type); + } + + /// Move `expr` into a temporary variable and execute `func` on that variable. + /// + /// Returns an expression that wraps both the creation and initialization of + /// the temporary, and the computation created by `func`. + /// + template<typename F> + RefPtr<Expr> SemanticsVisitor::moveTemp(RefPtr<Expr> const& expr, F const& func) + { + RefPtr<VarDecl> varDecl = new VarDecl(); + varDecl->ParentDecl = nullptr; // TODO: need to fill this in somehow! + varDecl->checkState = DeclCheckState::Checked; + varDecl->nameAndLoc.loc = expr->loc; + varDecl->initExpr = expr; + varDecl->type.type = expr->type.type; + + auto varDeclRef = makeDeclRef(varDecl.Ptr()); + + RefPtr<LetExpr> letExpr = new LetExpr(); + letExpr->decl = varDecl; + + auto body = func(varDeclRef); + + letExpr->body = body; + letExpr->type = body->type; + + return letExpr; + } + + /// Execute `func` on a variable with the value of `expr`. + /// + /// If `expr` is just a reference to an immutable (e.g., `let`) variable + /// then this might use the existing variable. Otherwise it will create + /// a new variable to hold `expr`, using `moveTemp()`. + /// + template<typename F> + RefPtr<Expr> SemanticsVisitor::maybeMoveTemp(RefPtr<Expr> const& expr, F const& func) + { + if(auto varExpr = as<VarExpr>(expr)) + { + auto declRef = varExpr->declRef; + if(auto varDeclRef = declRef.as<LetDecl>()) + return func(varDeclRef); + } + + return moveTemp(expr, func); + } + + /// Return an expression that represents "opening" the existential `expr`. + /// + /// The type of `expr` must be an interface type, matching `interfaceDeclRef`. + /// + /// If we scope down the PL theory to just the case that Slang cares about, + /// a value of an existential type like `IMover` is a tuple of: + /// + /// * a concrete type `X` + /// * a witness `w` of the fact that `X` implements `IMover` + /// * a value `v` of type `X` + /// + /// "Opening" an existential value is the process of decomposing a single + /// value `e : IMover` into the pieces `X`, `w`, and `v`. + /// + /// Rather than return all those pieces individually, this operation + /// returns an expression that logically corresponds to `v`: an expression + /// of type `X`, where the type carries the knowledge that `X` implements `IMover`. + /// + RefPtr<Expr> SemanticsVisitor::openExistential( + RefPtr<Expr> expr, + DeclRef<InterfaceDecl> interfaceDeclRef) + { + // If `expr` refers to an immutable binding, + // then we can use it directly. If it refers + // to an arbitrary expression or a mutable + // binding, we will move its value into an + // immutable temporary so that we can use + // it directly. + // + auto interfaceDecl = interfaceDeclRef.getDecl(); + return maybeMoveTemp(expr, [&](DeclRef<VarDeclBase> varDeclRef) + { + RefPtr<ExtractExistentialType> openedType = new ExtractExistentialType(); + openedType->declRef = varDeclRef; + + RefPtr<ExtractExistentialSubtypeWitness> openedWitness = new ExtractExistentialSubtypeWitness(); + openedWitness->sub = openedType; + openedWitness->sup = expr->type.type; + openedWitness->declRef = varDeclRef; + + RefPtr<ThisTypeSubstitution> openedThisType = new ThisTypeSubstitution(); + openedThisType->outer = interfaceDeclRef.substitutions.substitutions; + openedThisType->interfaceDecl = interfaceDecl; + openedThisType->witness = openedWitness; + + DeclRef<InterfaceDecl> substDeclRef = DeclRef<InterfaceDecl>(interfaceDecl, openedThisType); + auto substDeclRefType = DeclRefType::Create(getSession(), substDeclRef); + + RefPtr<ExtractExistentialValueExpr> openedValue = new ExtractExistentialValueExpr(); + openedValue->declRef = varDeclRef; + openedValue->type = QualType(substDeclRefType); + + return openedValue; + }); + } + + /// If `expr` has existential type, then open it. + /// + /// Returns an expression that opens `expr` if it had existential type. + /// Otherwise returns `expr` itself. + /// + /// See `openExistential` for a discussion of what "opening" an + /// existential-type value means. + /// + RefPtr<Expr> SemanticsVisitor::maybeOpenExistential(RefPtr<Expr> expr) + { + auto exprType = expr->type.type; + + if(auto declRefType = as<DeclRefType>(exprType)) + { + if(auto interfaceDeclRef = declRefType->declRef.as<InterfaceDecl>()) + { + // Is there an this-type substitution being applied, so that + // we are referencing the interface type through a concrete + // type (e.g., a type parameter constrained to this interface)? + // + // Because of the way that substitutions need to mirror the nesting + // hierarchy of declarations, any this-type substitution pertaining + // to the chosen interface decl must be the first substitution on + // the list (which is a linked list from the "inside" out). + // + auto thisTypeSubst = interfaceDeclRef.substitutions.substitutions.as<ThisTypeSubstitution>(); + if(thisTypeSubst && thisTypeSubst->interfaceDecl == interfaceDeclRef.decl) + { + // This isn't really an existential type, because somebody + // has already filled in a this-type substitution. + } + else + { + // Okay, here is the case that matters. + // + return openExistential(expr, interfaceDeclRef); + } + } + } + + // Default: apply the callback to the original expression; + return expr; + } + + RefPtr<Expr> SemanticsVisitor::ConstructDeclRefExpr( + DeclRef<Decl> declRef, + RefPtr<Expr> baseExpr, + SourceLoc loc) + { + // Compute the type that this declaration reference will have in context. + // + auto type = GetTypeForDeclRef(declRef); + + // Construct an appropriate expression based on the structured of + // the declaration reference. + // + if (baseExpr) + { + // If there was a base expression, we will have some kind of + // member expression. + + // We want to check for the case where the base "expression" + // actually names a type, because in that case we are doing + // a static member reference. + // + // TODO: Should we be checking if the member is static here? + // If it isn't, should we be automatically producing a "curried" + // form (e.g., for a member function, return a value usable + // for referencing it as a free function). + // + if (as<TypeType>(baseExpr->type)) + { + auto expr = new StaticMemberExpr(); + expr->loc = loc; + expr->type = type; + expr->BaseExpression = baseExpr; + expr->name = declRef.GetName(); + expr->declRef = declRef; + return expr; + } + else if(isEffectivelyStatic(declRef.getDecl())) + { + // Extract the type of the baseExpr + auto baseExprType = baseExpr->type.type; + RefPtr<SharedTypeExpr> baseTypeExpr = new SharedTypeExpr(); + baseTypeExpr->base.type = baseExprType; + baseTypeExpr->type.type = getTypeType(baseExprType); + + auto expr = new StaticMemberExpr(); + expr->loc = loc; + expr->type = type; + expr->BaseExpression = baseTypeExpr; + expr->name = declRef.GetName(); + expr->declRef = declRef; + return expr; + } + else + { + // If the base expression wasn't a type, then this + // is a normal member expression. + // + auto expr = new MemberExpr(); + expr->loc = loc; + expr->type = type; + expr->BaseExpression = baseExpr; + expr->name = declRef.GetName(); + expr->declRef = declRef; + + // When referring to a member through an expression, + // the result is only an l-value if both the base + // expression and the member agree that it should be. + // + // We have already used the `QualType` from the member + // above (that is `type`), so we need to take the + // l-value status of the base expression into account now. + if(!baseExpr->type.IsLeftValue) + { + expr->type.IsLeftValue = false; + } + + return expr; + } + } + else + { + // If there is no base expression, then the result must + // be an ordinary variable expression. + // + auto expr = new VarExpr(); + expr->loc = loc; + expr->name = declRef.GetName(); + expr->type = type; + expr->declRef = declRef; + return expr; + } + } + + RefPtr<Expr> SemanticsVisitor::ConstructDerefExpr( + RefPtr<Expr> base, + SourceLoc loc) + { + auto ptrLikeType = as<PointerLikeType>(base->type); + SLANG_ASSERT(ptrLikeType); + + auto derefExpr = new DerefExpr(); + derefExpr->loc = loc; + derefExpr->base = base; + derefExpr->type = QualType(ptrLikeType->elementType); + + // TODO(tfoley): handle l-value status here + + return derefExpr; + } + + RefPtr<Expr> SemanticsVisitor::createImplicitThisMemberExpr( + Type* type, + SourceLoc loc, + LookupResultItem::Breadcrumb::ThisParameterMode thisParameterMode) + { + RefPtr<ThisExpr> expr = new ThisExpr(); + expr->type = type; + expr->type.IsLeftValue = thisParameterMode == LookupResultItem::Breadcrumb::ThisParameterMode::Mutating; + expr->loc = loc; + return expr; + } + + RefPtr<Expr> SemanticsVisitor::ConstructLookupResultExpr( + LookupResultItem const& item, + RefPtr<Expr> baseExpr, + SourceLoc loc) + { + // If we collected any breadcrumbs, then these represent + // additional segments of the lookup path that we need + // to expand here. + auto bb = baseExpr; + for (auto breadcrumb = item.breadcrumbs; breadcrumb; breadcrumb = breadcrumb->next) + { + switch (breadcrumb->kind) + { + case LookupResultItem::Breadcrumb::Kind::Member: + bb = ConstructDeclRefExpr(breadcrumb->declRef, bb, loc); + break; + + case LookupResultItem::Breadcrumb::Kind::Deref: + bb = ConstructDerefExpr(bb, loc); + break; + + case LookupResultItem::Breadcrumb::Kind::Constraint: + { + // TODO: do we need to make something more + // explicit here? + bb = ConstructDeclRefExpr( + breadcrumb->declRef, + bb, + loc); + } + break; + + case LookupResultItem::Breadcrumb::Kind::This: + { + // We expect a `this` to always come + // at the start of a chain. + SLANG_ASSERT(bb == nullptr); + + // The member was looked up via a `this` expression, + // so we need to create one here. + if (auto extensionDeclRef = breadcrumb->declRef.as<ExtensionDecl>()) + { + bb = createImplicitThisMemberExpr( + GetTargetType(extensionDeclRef), + loc, + breadcrumb->thisParameterMode); + } + else + { + auto type = DeclRefType::Create(getSession(), breadcrumb->declRef); + bb = createImplicitThisMemberExpr( + type, + loc, + breadcrumb->thisParameterMode); + } + } + break; + + default: + SLANG_UNREACHABLE("all cases handle"); + } + } + + return ConstructDeclRefExpr(item.declRef, bb, loc); + } + + RefPtr<Expr> SemanticsVisitor::createLookupResultExpr( + LookupResult const& lookupResult, + RefPtr<Expr> baseExpr, + SourceLoc loc) + { + if (lookupResult.isOverloaded()) + { + auto overloadedExpr = new OverloadedExpr(); + overloadedExpr->loc = loc; + overloadedExpr->type = QualType( + getSession()->getOverloadedType()); + overloadedExpr->base = baseExpr; + overloadedExpr->lookupResult2 = lookupResult; + return overloadedExpr; + } + else + { + return ConstructLookupResultExpr(lookupResult.item, baseExpr, loc); + } + } + + RefPtr<Expr> SemanticsVisitor::ResolveOverloadedExpr(RefPtr<OverloadedExpr> overloadedExpr, LookupMask mask) + { + auto lookupResult = overloadedExpr->lookupResult2; + SLANG_RELEASE_ASSERT(lookupResult.isValid() && lookupResult.isOverloaded()); + + // Take the lookup result we had, and refine it based on what is expected in context. + lookupResult = refineLookup(lookupResult, mask); + + if (!lookupResult.isValid()) + { + // If we didn't find any symbols after filtering, then just + // use the original and report errors that way + return overloadedExpr; + } + + if (lookupResult.isOverloaded()) + { + // We had an ambiguity anyway, so report it. + getSink()->diagnose(overloadedExpr, Diagnostics::ambiguousReference, lookupResult.items[0].declRef.GetName()); + + for(auto item : lookupResult.items) + { + String declString = getDeclSignatureString(item); + getSink()->diagnose(item.declRef, Diagnostics::overloadCandidate, declString); + } + + // TODO(tfoley): should we construct a new ErrorExpr here? + return CreateErrorExpr(overloadedExpr); + } + + // otherwise, we had a single decl and it was valid, hooray! + return ConstructLookupResultExpr(lookupResult.item, overloadedExpr->base, overloadedExpr->loc); + } + + RefPtr<Expr> SemanticsVisitor::CheckTerm(RefPtr<Expr> term) + { + if (!term) return nullptr; + return ExprVisitor::dispatch(term); + } + + RefPtr<Expr> SemanticsVisitor::CreateErrorExpr(Expr* expr) + { + expr->type = QualType(getSession()->getErrorType()); + return expr; + } + + bool SemanticsVisitor::IsErrorExpr(RefPtr<Expr> expr) + { + // TODO: we may want other cases here... + + if (auto errorType = as<ErrorType>(expr->type)) + return true; + + return false; + } + + RefPtr<Expr> SemanticsVisitor::GetBaseExpr(RefPtr<Expr> expr) + { + if (auto memberExpr = as<MemberExpr>(expr)) + { + return memberExpr->BaseExpression; + } + else if(auto overloadedExpr = as<OverloadedExpr>(expr)) + { + return overloadedExpr->base; + } + return nullptr; + } + + RefPtr<Expr> SemanticsVisitor::visitBoolLiteralExpr(BoolLiteralExpr* expr) + { + expr->type = getSession()->getBoolType(); + return expr; + } + + RefPtr<Expr> SemanticsVisitor::visitIntegerLiteralExpr(IntegerLiteralExpr* expr) + { + // The expression might already have a type, determined by its suffix. + // It it doesn't, we will give it a default type. + // + // TODO: We should be careful to pick a "big enough" type + // based on the size of the value (e.g., don't try to stuff + // a constant in an `int` if it requires 64 or more bits). + // + // The long-term solution here is to give a type to a literal + // based on the context where it is used, but that requires + // a more sophisticated type system than we have today. + // + if(!expr->type.type) + { + expr->type = getSession()->getIntType(); + } + return expr; + } + + RefPtr<Expr> SemanticsVisitor::visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) + { + if(!expr->type.type) + { + expr->type = getSession()->getFloatType(); + } + return expr; + } + + RefPtr<Expr> SemanticsVisitor::visitStringLiteralExpr(StringLiteralExpr* expr) + { + expr->type = getSession()->getStringType(); + return expr; + } + + IntVal* SemanticsVisitor::GetIntVal(IntegerLiteralExpr* expr) + { + // TODO(tfoley): don't keep allocating here! + return new ConstantIntVal(expr->value); + } + + RefPtr<IntVal> SemanticsVisitor::TryConstantFoldExpr( + InvokeExpr* invokeExpr) + { + // We need all the operands to the expression + + // Check if the callee is an operation that is amenable to constant-folding. + // + // For right now we will look for calls to intrinsic functions, and then inspect + // their names (this is bad and slow). + auto funcDeclRefExpr = invokeExpr->FunctionExpr.as<DeclRefExpr>(); + if (!funcDeclRefExpr) return nullptr; + + auto funcDeclRef = funcDeclRefExpr->declRef; + auto intrinsicMod = funcDeclRef.getDecl()->FindModifier<IntrinsicOpModifier>(); + if (!intrinsicMod) + { + // We can't constant fold anything that doesn't map to a builtin + // operation right now. + // + // TODO: we should really allow constant-folding for anything + // that can be lowered to our bytecode... + return nullptr; + } + + + + // Let's not constant-fold operations with more than a certain number of arguments, for simplicity + static const int kMaxArgs = 8; + if (invokeExpr->Arguments.getCount() > kMaxArgs) + return nullptr; + + // Before checking the operation name, let's look at the arguments + RefPtr<IntVal> argVals[kMaxArgs]; + IntegerLiteralValue constArgVals[kMaxArgs]; + int argCount = 0; + bool allConst = true; + for (auto argExpr : invokeExpr->Arguments) + { + auto argVal = TryCheckIntegerConstantExpression(argExpr.Ptr()); + if (!argVal) + return nullptr; + + argVals[argCount] = argVal; + + if (auto constArgVal = as<ConstantIntVal>(argVal)) + { + constArgVals[argCount] = constArgVal->value; + } + else + { + allConst = false; + } + argCount++; + } + + if (!allConst) + { + // TODO(tfoley): We probably want to support a very limited number of operations + // on "constants" that aren't actually known, to be able to handle a generic + // that takes an integer `N` but then constructs a vector of size `N+1`. + // + // The hard part there is implementing the rules for value unification in the + // presence of more complicated `IntVal` subclasses, like `SumIntVal`. You'd + // need inference to be smart enough to know that `2 + N` and `N + 2` are the + // same value, as are `N + M + 1 + 1` and `M + 2 + N`. + // + // For now we can just bail in this case. + return nullptr; + } + + // At this point, all the operands had simple integer values, so we are golden. + IntegerLiteralValue resultValue = 0; + auto opName = funcDeclRef.GetName(); + + // handle binary operators + if (opName == getName("-")) + { + if (argCount == 1) + { + resultValue = -constArgVals[0]; + } + else if (argCount == 2) + { + resultValue = constArgVals[0] - constArgVals[1]; + } + } + + // simple binary operators +#define CASE(OP) \ + else if(opName == getName(#OP)) do { \ + if(argCount != 2) return nullptr; \ + resultValue = constArgVals[0] OP constArgVals[1]; \ + } while(0) + + CASE(+); // TODO: this can also be unary... + CASE(*); + CASE(<<); + CASE(>>); + CASE(&); + CASE(|); + CASE(^); +#undef CASE + + // binary operators with chance of divide-by-zero + // TODO: issue a suitable error in that case +#define CASE(OP) \ + else if(opName == getName(#OP)) do { \ + if(argCount != 2) return nullptr; \ + if(!constArgVals[1]) return nullptr; \ + resultValue = constArgVals[0] OP constArgVals[1]; \ + } while(0) + + CASE(/); + CASE(%); +#undef CASE + + // TODO(tfoley): more cases + else + { + return nullptr; + } + + RefPtr<IntVal> result = new ConstantIntVal(resultValue); + return result; + } + + RefPtr<IntVal> SemanticsVisitor::TryConstantFoldExpr( + Expr* expr) + { + // Unwrap any "identity" expressions + while (auto parenExpr = as<ParenExpr>(expr)) + { + expr = parenExpr->base; + } + + // TODO(tfoley): more serious constant folding here + if (auto intLitExpr = as<IntegerLiteralExpr>(expr)) + { + return GetIntVal(intLitExpr); + } + + // it is possible that we are referring to a generic value param + if (auto declRefExpr = as<DeclRefExpr>(expr)) + { + auto declRef = declRefExpr->declRef; + + if (auto genericValParamRef = declRef.as<GenericValueParamDecl>()) + { + // TODO(tfoley): handle the case of non-`int` value parameters... + return new GenericParamIntVal(genericValParamRef); + } + + // We may also need to check for references to variables that + // are defined in a way that can be used as a constant expression: + if(auto varRef = declRef.as<VarDeclBase>()) + { + auto varDecl = varRef.getDecl(); + + // In HLSL, `static const` is used to mark compile-time constant expressions + if(auto staticAttr = varDecl->FindModifier<HLSLStaticModifier>()) + { + if(auto constAttr = varDecl->FindModifier<ConstModifier>()) + { + // HLSL `static const` can be used as a constant expression + if(auto initExpr = getInitExpr(varRef)) + { + return TryConstantFoldExpr(initExpr.Ptr()); + } + } + } + } + else if(auto enumRef = declRef.as<EnumCaseDecl>()) + { + // The cases in an `enum` declaration can also be used as constant expressions, + if(auto tagExpr = getTagExpr(enumRef)) + { + return TryConstantFoldExpr(tagExpr.Ptr()); + } + } + } + + if(auto castExpr = as<TypeCastExpr>(expr)) + { + auto val = TryConstantFoldExpr(castExpr->Arguments[0].Ptr()); + if(val) + return val; + } + else if (auto invokeExpr = as<InvokeExpr>(expr)) + { + auto val = TryConstantFoldExpr(invokeExpr); + if (val) + return val; + } + + return nullptr; + } + + RefPtr<IntVal> SemanticsVisitor::TryCheckIntegerConstantExpression(Expr* exp) + { + // Check if type is acceptable for an integer constant expression + if(auto basicType = as<BasicExpressionType>(exp->type.type)) + { + if(!isIntegerBaseType(basicType->baseType)) + return nullptr; + } + else + { + return nullptr; + } + + // Consider operations that we might be able to constant-fold... + return TryConstantFoldExpr(exp); + } + + RefPtr<IntVal> SemanticsVisitor::CheckIntegerConstantExpression(Expr* inExpr) + { + // No need to issue further errors if the expression didn't even type-check. + if(IsErrorExpr(inExpr)) return nullptr; + + // First coerce the expression to the expected type + auto expr = coerce(getSession()->getIntType(),inExpr); + + // No need to issue further errors if the type coercion failed. + if(IsErrorExpr(expr)) return nullptr; + + auto result = TryCheckIntegerConstantExpression(expr.Ptr()); + if (!result) + { + getSink()->diagnose(expr, Diagnostics::expectedIntegerConstantNotConstant); + } + return result; + } + + RefPtr<IntVal> SemanticsVisitor::CheckEnumConstantExpression(Expr* expr) + { + // No need to issue further errors if the expression didn't even type-check. + if(IsErrorExpr(expr)) return nullptr; + + // No need to issue further errors if the type coercion failed. + if(IsErrorExpr(expr)) return nullptr; + + auto result = TryConstantFoldExpr(expr); + if (!result) + { + getSink()->diagnose(expr, Diagnostics::expectedIntegerConstantNotConstant); + } + return result; + } + + RefPtr<Expr> SemanticsVisitor::CheckSimpleSubscriptExpr( + RefPtr<IndexExpr> subscriptExpr, + RefPtr<Type> elementType) + { + auto baseExpr = subscriptExpr->BaseExpression; + auto indexExpr = subscriptExpr->IndexExpression; + + if (!indexExpr->type->Equals(getSession()->getIntType()) && + !indexExpr->type->Equals(getSession()->getUIntType())) + { + getSink()->diagnose(indexExpr, Diagnostics::subscriptIndexNonInteger); + return CreateErrorExpr(subscriptExpr.Ptr()); + } + + subscriptExpr->type = QualType(elementType); + + // TODO(tfoley): need to be more careful about this stuff + subscriptExpr->type.IsLeftValue = baseExpr->type.IsLeftValue; + + return subscriptExpr; + } + + RefPtr<Expr> SemanticsVisitor::visitIndexExpr(IndexExpr* subscriptExpr) + { + auto baseExpr = subscriptExpr->BaseExpression; + baseExpr = CheckExpr(baseExpr); + + RefPtr<Expr> indexExpr = subscriptExpr->IndexExpression; + if (indexExpr) + { + indexExpr = CheckExpr(indexExpr); + } + + subscriptExpr->BaseExpression = baseExpr; + subscriptExpr->IndexExpression = indexExpr; + + // If anything went wrong in the base expression, + // then just move along... + if (IsErrorExpr(baseExpr)) + return CreateErrorExpr(subscriptExpr); + + // Otherwise, we need to look at the type of the base expression, + // to figure out how subscripting should work. + auto baseType = baseExpr->type.Ptr(); + if (auto baseTypeType = as<TypeType>(baseType)) + { + // We are trying to "index" into a type, so we have an expression like `float[2]` + // which should be interpreted as resolving to an array type. + + RefPtr<IntVal> elementCount = nullptr; + if (indexExpr) + { + elementCount = CheckIntegerConstantExpression(indexExpr.Ptr()); + } + + auto elementType = CoerceToUsableType(TypeExp(baseExpr, baseTypeType->type)); + auto arrayType = getArrayType( + elementType, + elementCount); + + typeResult = arrayType; + subscriptExpr->type = QualType(getTypeType(arrayType)); + return subscriptExpr; + } + else if (auto baseArrayType = as<ArrayExpressionType>(baseType)) + { + return CheckSimpleSubscriptExpr( + subscriptExpr, + baseArrayType->baseType); + } + else if (auto vecType = as<VectorExpressionType>(baseType)) + { + return CheckSimpleSubscriptExpr( + subscriptExpr, + vecType->elementType); + } + else if (auto matType = as<MatrixExpressionType>(baseType)) + { + // TODO(tfoley): We shouldn't go and recompute + // row types over and over like this... :( + auto rowType = createVectorType( + matType->getElementType(), + matType->getColumnCount()); + + return CheckSimpleSubscriptExpr( + subscriptExpr, + rowType); + } + + // Default behavior is to look at all available `__subscript` + // declarations on the type and try to call one of them. + + { + LookupResult lookupResult = lookUpMember( + getSession(), + this, + getName("operator[]"), + baseType); + if (!lookupResult.isValid()) + { + goto fail; + } + + // Now that we know there is at least one subscript member, + // we will construct a reference to it and try to call it. + // + // Note: the expression may be an `OverloadedExpr`, in which + // case the attempt to call it will trigger overload + // resolution. + RefPtr<Expr> subscriptFuncExpr = createLookupResultExpr( + lookupResult, subscriptExpr->BaseExpression, subscriptExpr->loc); + + RefPtr<InvokeExpr> subscriptCallExpr = new InvokeExpr(); + subscriptCallExpr->loc = subscriptExpr->loc; + subscriptCallExpr->FunctionExpr = subscriptFuncExpr; + + // TODO(tfoley): This path can support multiple arguments easily + subscriptCallExpr->Arguments.add(subscriptExpr->IndexExpression); + + return CheckInvokeExprWithCheckedOperands(subscriptCallExpr.Ptr()); + } + + fail: + { + getSink()->diagnose(subscriptExpr, Diagnostics::subscriptNonArray, baseType); + return CreateErrorExpr(subscriptExpr); + } + } + + RefPtr<Expr> SemanticsVisitor::visitParenExpr(ParenExpr* expr) + { + auto base = expr->base; + base = CheckTerm(base); + + expr->base = base; + expr->type = base->type; + return expr; + } + + void SemanticsVisitor::maybeDiagnoseThisNotLValue(Expr* expr) + { + // We will try to handle expressions of the form: + // + // e ::= "this" + // | e . name + // | e [ expr ] + // + // We will unwrap the `e.name` and `e[expr]` cases in a loop. + RefPtr<Expr> e = expr; + for(;;) + { + if(auto memberExpr = as<MemberExpr>(e)) + { + e = memberExpr->BaseExpression; + } + else if(auto subscriptExpr = as<IndexExpr>(e)) + { + e = subscriptExpr->BaseExpression; + } + else + { + break; + } + } + // + // Now we check to see if we have a `this` expression, + // and if it is immutable. + if(auto thisExpr = as<ThisExpr>(e)) + { + if(!thisExpr->type.IsLeftValue) + { + getSink()->diagnose(thisExpr, Diagnostics::thisIsImmutableByDefault); + } + } + } + + RefPtr<Expr> SemanticsVisitor::visitAssignExpr(AssignExpr* expr) + { + expr->left = CheckExpr(expr->left); + + auto type = expr->left->type; + + expr->right = coerce(type, CheckTerm(expr->right)); + + if (!type.IsLeftValue) + { + if (as<ErrorType>(type)) + { + // Don't report an l-value issue on an erroneous expression + } + else + { + getSink()->diagnose(expr, Diagnostics::assignNonLValue); + + // As a special case, check if the LHS expression is derived + // from a `this` parameter (implicitly or explicitly), which + // is immutable. We can give the user a bit more context into + // what is going on. + // + maybeDiagnoseThisNotLValue(expr->left); + } + } + expr->type = type; + return expr; + } + + RefPtr<Expr> SemanticsVisitor::CheckExpr(RefPtr<Expr> expr) + { + auto term = CheckTerm(expr); + + // TODO(tfoley): Need a step here to ensure that the term actually + // resolves to a (single) expression with a real type. + + return term; + } + + RefPtr<Expr> SemanticsVisitor::CheckInvokeExprWithCheckedOperands(InvokeExpr *expr) + { + auto rs = ResolveInvoke(expr); + if (auto invoke = as<InvokeExpr>(rs.Ptr())) + { + // if this is still an invoke expression, test arguments passed to inout/out parameter are LValues + if(auto funcType = as<FuncType>(invoke->FunctionExpr->type)) + { + Index paramCount = funcType->getParamCount(); + for (Index pp = 0; pp < paramCount; ++pp) + { + auto paramType = funcType->getParamType(pp); + if (as<OutTypeBase>(paramType) || as<RefType>(paramType)) + { + // `out`, `inout`, and `ref` parameters currently require + // an *exact* match on the type of the argument. + // + // TODO: relax this requirement by allowing an argument + // for an `inout` parameter to be converted in both + // directions. + // + if( pp < expr->Arguments.getCount() ) + { + auto argExpr = expr->Arguments[pp]; + if( !argExpr->type.IsLeftValue ) + { + getSink()->diagnose( + argExpr, + Diagnostics::argumentExpectedLValue, + pp); + + if( auto implicitCastExpr = as<ImplicitCastExpr>(argExpr) ) + { + getSink()->diagnose( + argExpr, + Diagnostics::implicitCastUsedAsLValue, + implicitCastExpr->Arguments[0]->type, + implicitCastExpr->type); + } + + maybeDiagnoseThisNotLValue(argExpr); + } + } + else + { + // There are two ways we could get here, both involving + // a call where the number of argument expressions is + // less than the number of parameters on the callee: + // + // 1. There might be fewer arguments than parameters + // because the trailing parameters should be defaulted + // + // 2. There might be fewer arguments than parameters + // because the call is incorrect. + // + // In case (2) an error would have already been diagnosed, + // and we don't want to emit another cascading error here. + // + // In case (1) this implies the user declared an `out` + // or `inout` parameter with a default argument expression. + // That should be an error, but it should be detected + // on the declaration instead of here at the use site. + // + // Thus, it makes sense to ignore this case here. + } + } + } + } + } + return rs; + } + + RefPtr<Expr> SemanticsVisitor::visitInvokeExpr(InvokeExpr *expr) + { + // check the base expression first + expr->FunctionExpr = CheckExpr(expr->FunctionExpr); + // Next check the argument expressions + for (auto & arg : expr->Arguments) + { + arg = CheckExpr(arg); + } + + return CheckInvokeExprWithCheckedOperands(expr); + } + + RefPtr<Expr> SemanticsVisitor::visitVarExpr(VarExpr *expr) + { + // If we've already resolved this expression, don't try again. + if (expr->declRef) + return expr; + + expr->type = QualType(getSession()->getErrorType()); + auto lookupResult = lookUp( + getSession(), + this, expr->name, expr->scope); + if (lookupResult.isValid()) + { + return createLookupResultExpr( + lookupResult, + nullptr, + expr->loc); + } + + getSink()->diagnose(expr, Diagnostics::undefinedIdentifier2, expr->name); + + return expr; + } + + RefPtr<Expr> SemanticsVisitor::visitTypeCastExpr(TypeCastExpr * expr) + { + // Check the term we are applying first + auto funcExpr = expr->FunctionExpr; + funcExpr = CheckTerm(funcExpr); + + // Now ensure that the term represnets a (proper) type. + TypeExp typeExp; + typeExp.exp = funcExpr; + typeExp = CheckProperType(typeExp); + + expr->FunctionExpr = typeExp.exp; + expr->type.type = typeExp.type; + + // Next check the argument expression (there should be only one) + for (auto & arg : expr->Arguments) + { + arg = CheckExpr(arg); + } + + // LEGACY FEATURE: As a backwards-compatibility feature + // for HLSL, we will allow for a cast to a `struct` type + // from a literal zero, with the semantics of default + // initialization. + // + if( auto declRefType = typeExp.type.as<DeclRefType>() ) + { + if(auto structDeclRef = declRefType->declRef.as<StructDecl>()) + { + if( expr->Arguments.getCount() == 1 ) + { + auto arg = expr->Arguments[0]; + if( auto intLitArg = arg.as<IntegerLiteralExpr>() ) + { + if(getIntegerLiteralValue(intLitArg->token) == 0) + { + // At this point we have confirmed that the cast + // has the right form, so we want to apply our special case. + // + // TODO: If/when we allow for user-defined initializer/constructor + // definitions we would have to be careful here because it is + // possible that the target type has defined an initializer/constructor + // that takes a single `int` parmaeter and means to call that instead. + // + // For now that should be a non-issue, and in a pinch such a user + // could use `T(0)` instead of `(T) 0` to get around this special + // HLSL legacy feature. + + // We will type-check code like: + // + // MyStruct s = (MyStruct) 0; + // + // the same as: + // + // MyStruct s = {}; + // + // That is, we construct an empty initializer list, and then coerce + // that initializer list expression to the desired type (letting + // the code for handling initializer lists work out all of the + // details of what is/isn't valid). This choice means we get + // to benefit from the existing codegen support for initializer + // lists, rather than needing the `(MyStruct) 0` idiom to be + // special-cased in later stages of the compiler. + // + // Note: we use an empty initializer list `{}` instead of an + // initializer list with a single zero `{0}`, which is semantically + // significant if the first field of `MyStruct` had its own + // default initializer defined as part of the `struct` definition. + // Basically we have chosen to interpret the "cast from zero" syntax + // as sugar for default initialization, and *not* specifically + // for zero-initialization. That choice could be revisited if + // users express displeasure. For now there isn't enough usage + // of explicit default initializers for `struct` fields to + // make this a major concern (since they aren't supported in HLSL). + // + RefPtr<InitializerListExpr> initListExpr = new InitializerListExpr(); + auto checkedInitListExpr = visitInitializerListExpr(initListExpr); + return coerce(typeExp.type, initListExpr); + } + } + } + } + } + + + // Now process this like any other explicit call (so casts + // and constructor calls are semantically equivalent). + return CheckInvokeExprWithCheckedOperands(expr); + } + + RefPtr<Expr> SemanticsVisitor::MaybeDereference(RefPtr<Expr> inExpr) + { + RefPtr<Expr> expr = inExpr; + for (;;) + { + auto baseType = expr->type; + if (auto pointerLikeType = as<PointerLikeType>(baseType)) + { + auto elementType = QualType(pointerLikeType->elementType); + elementType.IsLeftValue = baseType.IsLeftValue; + + auto derefExpr = new DerefExpr(); + derefExpr->base = expr; + derefExpr->type = elementType; + + expr = derefExpr; + continue; + } + + // Default case: just use the expression as-is + return expr; + } + } + + RefPtr<Expr> SemanticsVisitor::CheckSwizzleExpr( + MemberExpr* memberRefExpr, + RefPtr<Type> baseElementType, + IntegerLiteralValue baseElementCount) + { + RefPtr<SwizzleExpr> swizExpr = new SwizzleExpr(); + swizExpr->loc = memberRefExpr->loc; + swizExpr->base = memberRefExpr->BaseExpression; + + IntegerLiteralValue limitElement = baseElementCount; + + int elementIndices[4]; + int elementCount = 0; + + bool elementUsed[4] = { false, false, false, false }; + bool anyDuplicates = false; + bool anyError = false; + + auto swizzleText = getText(memberRefExpr->name); + + for (Index i = 0; i < swizzleText.getLength(); i++) + { + auto ch = swizzleText[i]; + int elementIndex = -1; + switch (ch) + { + case 'x': case 'r': elementIndex = 0; break; + case 'y': case 'g': elementIndex = 1; break; + case 'z': case 'b': elementIndex = 2; break; + case 'w': case 'a': elementIndex = 3; break; + default: + // An invalid character in the swizzle is an error + getSink()->diagnose(swizExpr, Diagnostics::invalidSwizzleExpr, swizzleText, baseElementType->ToString()); + anyError = true; + continue; + } + + // TODO(tfoley): GLSL requires that all component names + // come from the same "family"... + + // Make sure the index is in range for the source type + if (elementIndex >= limitElement) + { + getSink()->diagnose(swizExpr, Diagnostics::invalidSwizzleExpr, swizzleText, baseElementType->ToString()); + anyError = true; + continue; + } + + // Check if we've seen this index before + for (int ee = 0; ee < elementCount; ee++) + { + if (elementIndices[ee] == elementIndex) + anyDuplicates = true; + } + + // add to our list... + elementIndices[elementCount++] = elementIndex; + } + + for (int ee = 0; ee < elementCount; ++ee) + { + swizExpr->elementIndices[ee] = elementIndices[ee]; + } + swizExpr->elementCount = elementCount; + + if (anyError) + { + return CreateErrorExpr(memberRefExpr); + } + else if (elementCount == 1) + { + // single-component swizzle produces a scalar + // + // Note(tfoley): the official HLSL rules seem to be that it produces + // a one-component vector, which is then implicitly convertible to + // a scalar, but that seems like it just adds complexity. + swizExpr->type = QualType(baseElementType); + } + else + { + // TODO(tfoley): would be nice to "re-sugar" type + // here if the input type had a sugared name... + swizExpr->type = QualType(createVectorType( + baseElementType, + new ConstantIntVal(elementCount))); + } + + // A swizzle can be used as an l-value as long as there + // were no duplicates in the list of components + swizExpr->type.IsLeftValue = !anyDuplicates; + + return swizExpr; + } + + RefPtr<Expr> SemanticsVisitor::CheckSwizzleExpr( + MemberExpr* memberRefExpr, + RefPtr<Type> baseElementType, + RefPtr<IntVal> baseElementCount) + { + if (auto constantElementCount = as<ConstantIntVal>(baseElementCount)) + { + return CheckSwizzleExpr(memberRefExpr, baseElementType, constantElementCount->value); + } + else + { + getSink()->diagnose(memberRefExpr, Diagnostics::unimplemented, "swizzle on vector of unknown size"); + return CreateErrorExpr(memberRefExpr); + } + } + + RefPtr<Expr> SemanticsVisitor::_lookupStaticMember(RefPtr<DeclRefExpr> expr, RefPtr<Expr> baseExpression) + { + auto& baseType = baseExpression->type; + + if (auto typeType = as<TypeType>(baseType)) + { + // We are looking up a member inside a type. + // We want to be careful here because we should only find members + // that are implicitly or explicitly `static`. + // + // TODO: this duplicates a *lot* of logic with the case below. + // We need to fix that. + auto type = typeType->type; + + if (as<ErrorType>(type)) + { + return CreateErrorExpr(expr); + } + + LookupResult lookupResult = lookUpMember( + getSession(), + this, + expr->name, + type); + if (!lookupResult.isValid()) + { + return lookupMemberResultFailure(expr, baseType); + } + + // We need to confirm that whatever member we + // are trying to refer to is usable via static reference. + // + // TODO: eventually we might allow a non-static + // member to be adapted by turning it into something + // like a closure that takes the missing `this` parameter. + // + // E.g., a static reference to a method could be treated + // as a value with a function type, where the first parameter + // is `type`. + // + // The biggest challenge there is that we'd need to arrange + // to generate "dispatcher" functions that could be used + // to implement that function, in the case where we are + // making a static reference to some kind of polymorphic declaration. + // + // (Also, static references to fields/properties would get even + // harder, because you'd have to know whether a getter/setter/ref-er + // is needed). + // + // For now let's just be expedient and disallow all of that, because + // we can always add it back in later. + + if (!lookupResult.isOverloaded()) + { + // The non-overloaded case is relatively easy. We just want + // to look at the member being referenced, and check if + // it is allowed in a `static` context: + // + if (!isUsableAsStaticMember(lookupResult.item)) + { + getSink()->diagnose( + expr->loc, + Diagnostics::staticRefToNonStaticMember, + type, + expr->name); + } + } + else + { + // The overloaded case is trickier, because we should first + // filter the list of candidates, because if there is anything + // that *is* usable in a static context, then we should assume + // the user just wants to reference that. We should only + // issue an error if *all* of the items that were discovered + // are non-static. + bool anyNonStatic = false; + List<LookupResultItem> staticItems; + for (auto item : lookupResult.items) + { + // Is this item usable as a static member? + if (isUsableAsStaticMember(item)) + { + // If yes, then it will be part of the output. + staticItems.add(item); + } + else + { + // If no, then we might need to output an error. + anyNonStatic = true; + } + } + + // Was there anything non-static in the list? + if (anyNonStatic) + { + // If we had some static items, then that's okay, + // we just want to use our newly-filtered list. + if (staticItems.getCount()) + { + lookupResult.items = staticItems; + } + else + { + // Otherwise, it is time to report an error. + getSink()->diagnose( + expr->loc, + Diagnostics::staticRefToNonStaticMember, + type, + expr->name); + } + } + // If there were no non-static items, then the `items` + // array already represents what we'd get by filtering... + } + + return createLookupResultExpr( + lookupResult, + baseExpression, + expr->loc); + } + else if (as<ErrorType>(baseType)) + { + return CreateErrorExpr(expr); + } + + // Failure + return lookupMemberResultFailure(expr, baseType); + } + + RefPtr<Expr> SemanticsVisitor::visitStaticMemberExpr(StaticMemberExpr* expr) + { + expr->BaseExpression = CheckExpr(expr->BaseExpression); + + // Not sure this is needed -> but guess someone could do + expr->BaseExpression = MaybeDereference(expr->BaseExpression); + + // If the base of the member lookup has an interface type + // *without* a suitable this-type substitution, then we are + // trying to perform lookup on a value of existential type, + // and we should "open" the existential here so that we + // can expose its structure. + // + + expr->BaseExpression = maybeOpenExistential(expr->BaseExpression); + // Do a static lookup + return _lookupStaticMember(expr, expr->BaseExpression); + } + + RefPtr<Expr> SemanticsVisitor::lookupMemberResultFailure( + DeclRefExpr* expr, + QualType const& baseType) + { + // Check it's a member expression + SLANG_ASSERT(as<StaticMemberExpr>(expr) || as<MemberExpr>(expr)); + + getSink()->diagnose(expr, Diagnostics::noMemberOfNameInType, expr->name, baseType); + expr->type = QualType(getSession()->getErrorType()); + return expr; + } + + RefPtr<Expr> SemanticsVisitor::visitMemberExpr(MemberExpr * expr) + { + expr->BaseExpression = CheckExpr(expr->BaseExpression); + + expr->BaseExpression = MaybeDereference(expr->BaseExpression); + + // If the base of the member lookup has an interface type + // *without* a suitable this-type substitution, then we are + // trying to perform lookup on a value of existential type, + // and we should "open" the existential here so that we + // can expose its structure. + // + expr->BaseExpression = maybeOpenExistential(expr->BaseExpression); + + auto & baseType = expr->BaseExpression->type; + + // Note: Checking for vector types before declaration-reference types, + // because vectors are also declaration reference types... + // + // Also note: the way this is done right now means that the ability + // to swizzle vectors interferes with any chance of looking up + // members via extension, for vector or scalar types. + // + // TODO: Matrix swizzles probably need to be handled at some point. + if (auto baseVecType = as<VectorExpressionType>(baseType)) + { + return CheckSwizzleExpr( + expr, + baseVecType->elementType, + baseVecType->elementCount); + } + else if(auto baseScalarType = as<BasicExpressionType>(baseType)) + { + // Treat scalar like a 1-element vector when swizzling + return CheckSwizzleExpr( + expr, + baseScalarType, + 1); + } + else if(auto typeType = as<TypeType>(baseType)) + { + return _lookupStaticMember(expr, expr->BaseExpression); + } + else if (as<ErrorType>(baseType)) + { + return CreateErrorExpr(expr); + } + else + { + LookupResult lookupResult = lookUpMember( + getSession(), + this, + expr->name, + baseType.Ptr()); + if (!lookupResult.isValid()) + { + return lookupMemberResultFailure(expr, baseType); + } + + // TODO: need to filter for declarations that are valid to refer + // to in this context... + + return createLookupResultExpr( + lookupResult, + expr->BaseExpression, + expr->loc); + } + } + + RefPtr<Expr> SemanticsVisitor::visitInitializerListExpr(InitializerListExpr* expr) + { + // When faced with an initializer list, we first just check the sub-expressions blindly. + // Actually making them conform to a desired type will wait for when we know the desired + // type based on context. + + for( auto& arg : expr->args ) + { + arg = CheckTerm(arg); + } + + expr->type = getSession()->getInitializerListType(); + + return expr; + } + + // Perform semantic checking of an object-oriented `this` + // expression. + RefPtr<Expr> SemanticsVisitor::visitThisExpr(ThisExpr* expr) + { + // A `this` expression will default to immutable. + expr->type.IsLeftValue = false; + + // We will do an upwards search starting in the current + // scope, looking for a surrounding type (or `extension`) + // declaration that could be the referrant of the expression. + auto scope = expr->scope; + while (scope) + { + auto containerDecl = scope->containerDecl; + + if( auto funcDeclBase = as<FunctionDeclBase>(containerDecl) ) + { + if( funcDeclBase->HasModifier<MutatingAttribute>() ) + { + expr->type.IsLeftValue = true; + } + } + else if (auto aggTypeDecl = as<AggTypeDecl>(containerDecl)) + { + checkDecl(aggTypeDecl); + + // Okay, we are using `this` in the context of an + // aggregate type, so the expression should be + // of the corresponding type. + expr->type.type = DeclRefType::Create( + getSession(), + makeDeclRef(aggTypeDecl)); + return expr; + } + else if (auto extensionDecl = as<ExtensionDecl>(containerDecl)) + { + checkDecl(extensionDecl); + + // When `this` is used in the context of an `extension` + // declaration, then it should refer to an instance of + // the type being extended. + // + // TODO: There is potentially a small gotcha here that + // lookup through such a `this` expression should probably + // prioritize members declared in the current extension + // if there are multiple extensions in scope that add + // members with the same name... + // + expr->type.type = extensionDecl->targetType.type; + return expr; + } + + scope = scope->parent; + } + + getSink()->diagnose(expr, Diagnostics::thisExpressionOutsideOfTypeDecl); + return CreateErrorExpr(expr); + } +} |
