https://github.com/shader-slang/slang
Tip revision: 27fd4b453aeae4626a043ffaa471d83f4bd39cff authored by Robert Stepinski on 22 November 2019, 23:20:18 UTC
Add command line option to override language file extension (#1135)
Add command line option to override language file extension (#1135)
Tip revision: 27fd4b4
slang-check-expr.cpp
// 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);
}
}