Skip to content

Commit 8171a55

Browse files
author
Tim Foley
authored
Support "modern" declaration syntax as an option (shader-slang#792)
* Support "modern" declaration syntax as an option Fixed shader-slang#202 This change adds four new declaration keywords: The `let` and `var` keywords introduce immutable and mutable variables, respectively. They can only be used to declare a single variable at a time (unlike C declaration syntax), and they support inference of the variable's type from its initial-value expression. Examples: ``` let a : int = 1; // immutable with explicit type and initial-value expression let b = a + 1; // immutable, with type inferred var c : float; // mutable, with explicit type var d = b + c; // mutable, with type inferred ``` These declaration forms can be used wherever ordinary global, local, or member variable declarations appeared before. Right now they do not change rules about what is or is not considered a shader parameter. The `static` modifier should work on these forms as expected, but a `static let` variable is *not* the same as a `static const`, so an explicit `const` is still needed if you want that behavior. A `typealias` declaration introduces a named type alias, similar to `typedef`, but with more reasonable syntax. It inherits from the same AST class that `typedef` uses, so all of the code after parsing should be able to treat them as equivalent. To give a simple example: ``` // typedef int MyArray[3]; typealais MyArray = int[3]; ``` A `func` declaration introduces a function. Like `typealias` it re-uses the existing AST class, so there is no need for major changes after parsing. A `func` declaration uses a syntax similar to `let` variables for its parameters, and takes the (optional) result type in a trailing position. For example: ``` func myAdd(a: int, b: int) -> int { return a + b; } ``` If a `func` declaration leaves of the return type clause, the return type is assumed to be `void`. The main difference (beyond the trailing return type) is that the parameters of a `func`-declared function are immutable (unless they are `out`/`inout`). This change doesn't add support for declaring operator overloads with `func`, but that should be added later, and I'd like to make that the only way to declare such operations: ``` func +(left: MyType, right: MyType) -> MyType { ... } ``` The use of `:` for declaring parameter types here means that a function declared with modern syntax currently cannot include HLSL-style semantics on its parameters (or its result). We might consider introducing an `[attribute]`-based syntax for adding semantics to parameters if we think this is important, but for now it is fine to insist that users declare their entry points using traditional syntax. This change strives to avoid unecessary changes after parsing, but if the new syntax catches on with users there are some small ways we can take advantage of it for performance. In particular, since `let` declarations and parameters of modern-style functions are immutable, we do not need to generate read/write local temporaries for them during lowering to the IR (technically we can make the same optimization for `const` locals). In the process of implementing these new forms I also added a few subroutines to help share code better between existing cases in the parser. In particular, parsing of generic parameter lists on declarations that can be generic is now simplified and more unified. * Fixup: remove leftover debugging code * fixup: typos
1 parent eda82cb commit 8171a55

7 files changed

+329
-135
lines changed

source/slang/check.cpp

+95-27
Original file line numberDiff line numberDiff line change
@@ -2134,39 +2134,74 @@ namespace Slang
21342134

21352135
void CheckVarDeclCommon(RefPtr<VarDeclBase> varDecl)
21362136
{
2137-
if (function || checkingPhase == CheckingPhase::Header)
2137+
// A variable that didn't have an explicit type written must
2138+
// have its type inferred from the initial-value expresison.
2139+
//
2140+
if(!varDecl->type.exp)
21382141
{
2139-
TypeExp typeExp = CheckUsableType(varDecl->type);
2140-
varDecl->type = typeExp;
2141-
if (varDecl->type.Equals(getSession()->getVoidType()))
2142+
// In this case we need to perform all checking of the
2143+
// variable (including semantic checking of the initial-value
2144+
// expression) during the first phase of checking.
2145+
2146+
auto initExpr = varDecl->initExpr;
2147+
if(!initExpr)
21422148
{
2143-
getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid);
2149+
getSink()->diagnose(varDecl, Diagnostics::varWithoutTypeMustHaveInitializer);
2150+
varDecl->type.type = getSession()->getErrorType();
21442151
}
2145-
}
2152+
else
2153+
{
2154+
initExpr = CheckExpr(initExpr);
21462155

2147-
if (checkingPhase == CheckingPhase::Body)
2156+
// TODO: We might need some additional steps here to ensure
2157+
// that the type of the expression is one we are okay with
2158+
// inferring. E.g., if we ever decide that integer and floating-point
2159+
// literals have a distinct type from the standard int/float types,
2160+
// then we would need to "decay" a literal to an explicit type here.
2161+
2162+
varDecl->initExpr = initExpr;
2163+
varDecl->type.type = initExpr->type;
2164+
}
2165+
2166+
varDecl->SetCheckState(DeclCheckState::Checked);
2167+
}
2168+
else
21482169
{
2149-
if (auto initExpr = varDecl->initExpr)
2170+
if (function || checkingPhase == CheckingPhase::Header)
21502171
{
2151-
initExpr = CheckTerm(initExpr);
2152-
initExpr = Coerce(varDecl->type.Ptr(), initExpr);
2153-
varDecl->initExpr = initExpr;
2172+
TypeExp typeExp = CheckUsableType(varDecl->type);
2173+
varDecl->type = typeExp;
2174+
if (varDecl->type.Equals(getSession()->getVoidType()))
2175+
{
2176+
getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid);
2177+
}
2178+
}
21542179

2155-
// If this is an array variable, then we first want to give
2156-
// it a chance to infer an array size from its initializer
2157-
//
2158-
// TODO(tfoley): May need to extend this to handle the
2159-
// multi-dimensional case...
2160-
//
2161-
maybeInferArraySizeForVariable(varDecl);
2162-
//
2163-
// Next we want to make sure that the declared (or inferred)
2164-
// size for the array meets whatever language-specific
2165-
// constraints we want to enforce (e.g., disallow empty
2166-
// arrays in specific cases)
2167-
//
2168-
validateArraySizeForVariable(varDecl);
2180+
if (checkingPhase == CheckingPhase::Body)
2181+
{
2182+
if (auto initExpr = varDecl->initExpr)
2183+
{
2184+
initExpr = CheckTerm(initExpr);
2185+
initExpr = Coerce(varDecl->type.Ptr(), initExpr);
2186+
varDecl->initExpr = initExpr;
2187+
2188+
// If this is an array variable, then we first want to give
2189+
// it a chance to infer an array size from its initializer
2190+
//
2191+
// TODO(tfoley): May need to extend this to handle the
2192+
// multi-dimensional case...
2193+
//
2194+
maybeInferArraySizeForVariable(varDecl);
2195+
//
2196+
// Next we want to make sure that the declared (or inferred)
2197+
// size for the array meets whatever language-specific
2198+
// constraints we want to enforce (e.g., disallow empty
2199+
// arrays in specific cases)
2200+
//
2201+
validateArraySizeForVariable(varDecl);
2202+
}
21692203
}
2204+
21702205
}
21712206
varDecl->SetCheckState(getCheckedState());
21722207
}
@@ -4289,8 +4324,19 @@ namespace Slang
42894324
functionNode->SetCheckState(DeclCheckState::CheckingHeader);
42904325
auto oldFunc = this->function;
42914326
this->function = functionNode;
4292-
auto returnType = CheckProperType(functionNode->ReturnType);
4293-
functionNode->ReturnType = returnType;
4327+
4328+
auto resultType = functionNode->ReturnType;
4329+
if(resultType.exp)
4330+
{
4331+
resultType = CheckProperType(functionNode->ReturnType);
4332+
}
4333+
else
4334+
{
4335+
resultType = TypeExp(getSession()->getVoidType());
4336+
}
4337+
functionNode->ReturnType = resultType;
4338+
4339+
42944340
HashSet<Name*> paraNames;
42954341
for (auto & para : functionNode->GetParameters())
42964342
{
@@ -9535,6 +9581,28 @@ namespace Slang
95359581
if(isGlobalShaderParameter(varDeclRef.getDecl()))
95369582
isLValue = false;
95379583

9584+
// Variables declared with `let` are always immutable.
9585+
if(varDeclRef.As<LetDecl>())
9586+
isLValue = false;
9587+
9588+
// Generic value parameters are always immutable
9589+
if(varDeclRef.As<GenericValueParamDecl>())
9590+
isLValue = false;
9591+
9592+
// Function parameters declared in the "modern" style
9593+
// are immutable unless they have an `out` or `inout` modifier.
9594+
if( varDeclRef.As<ModernParamDecl>() )
9595+
{
9596+
// Note: the `inout` modifier AST class inherits from
9597+
// the class for the `out` modifier so that we can
9598+
// make simple checks like this.
9599+
//
9600+
if( !varDeclRef.getDecl()->HasModifier<OutModifier>() )
9601+
{
9602+
isLValue = false;
9603+
}
9604+
}
9605+
95389606
qualType.IsLeftValue = isLValue;
95399607
return qualType;
95409608
}

source/slang/decl-defs.h

+9
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ END_SYNTAX_CLASS()
5151
SYNTAX_CLASS(VarDecl, VarDeclBase)
5252
END_SYNTAX_CLASS()
5353

54+
// A variable declaration that is always immutable (whether local, global, or member variable)
55+
SYNTAX_CLASS(LetDecl, VarDecl)
56+
END_SYNTAX_CLASS()
57+
5458
// An `AggTypeDeclBase` captures the shared functionality
5559
// between true aggregate type declarations and extension
5660
// declarations:
@@ -165,6 +169,8 @@ SYNTAX_CLASS(TypeDefDecl, SimpleTypeDecl)
165169
SYNTAX_FIELD(TypeExp, type)
166170
END_SYNTAX_CLASS()
167171

172+
SIMPLE_SYNTAX_CLASS(TypeAliasDecl, TypeDefDecl)
173+
168174
// An 'assoctype' declaration, it is a container of inheritance clauses
169175
SYNTAX_CLASS(AssocTypeDecl, AggTypeDecl)
170176
END_SYNTAX_CLASS()
@@ -180,6 +186,9 @@ SIMPLE_SYNTAX_CLASS(ScopeDecl, ContainerDecl)
180186
// A function/initializer/subscript parameter (potentially mutable)
181187
SIMPLE_SYNTAX_CLASS(ParamDecl, VarDeclBase)
182188

189+
// A parameter of a function declared in "modern" types (immutable unless explicitly `out` or `inout`)
190+
SIMPLE_SYNTAX_CLASS(ModernParamDecl, ParamDecl)
191+
183192
// Base class for things that have parameter lists and can thus be applied to arguments ("called")
184193
ABSTRACT_SYNTAX_CLASS(CallableDecl, ContainerDecl)
185194
RAW(

source/slang/diagnostic-defs.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ DIAGNOSTIC(30501, Error, cannotUseInitializerListForArrayOfUnknownSize, "cannot
289289
DIAGNOSTIC(30502, Error, cannotUseInitializerListForVectorOfUnknownSize, "cannot use initializer list for vector of statically unknown size '$0'");
290290
DIAGNOSTIC(30503, Error, cannotUseInitializerListForMatrixOfUnknownSize, "cannot use initializer list for matrix of statically unknown size '$0' rows");
291291

292-
292+
// 306xx: variables
293+
DIAGNOSTIC(30600, Error, varWithoutTypeMustHaveInitializer, "a variable declaration without an initial-value expression must be given an explicit type");
293294

294295
DIAGNOSTIC(39999, Error, expectedIntegerConstantWrongType, "expected integer constant (found: '$0')")
295296
DIAGNOSTIC(39999, Error, expectedIntegerConstantNotConstant, "expression does not evaluate to a compile-time constant")

0 commit comments

Comments
 (0)