summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2018-08-27 12:33:35 -0700
committerGitHub <noreply@github.com>2018-08-27 12:33:35 -0700
commit9a9733091cc7c9628e445313785d561deb229072 (patch)
treec15aca8a54dc36ab5c1671591b86ae7e1895b382 /source
parent6a8ad6eb4cab72c18de48762768e04d08b60a21c (diff)
Add basic support for #pragma once (#630)
* Improve diagnostic messages for function redefinition The front-end was using internal "not implemented" errors instead of friendly user-facing errors to handle: * Redefinition of a function (same signature and both have bodies) * Multiple function declarations/definitions with the same parameter signature, but differnet return types This change simply turns both of these into reasonably friendly errors that explain what went wrong and point to the previous definition/declaration as appropriate. * Add support for detecting #pragma directives and handling them The logic here mirrors what was set up for preprocessor directives, just for "sub-directives" in this case. The only case here is the default one, which now reports a warning for directives we don't understand. * Add basic support for #pragma once Fixes #494 The approach here is simplistic in the extreme. When we see a `#pragma once` directive, we put the current file path (the location of the `#pragma` directive, as reported by our source manager) into a set, and then any paths in that set are ignored by subsequent `#include` directives. This should work for simple cases of `#pragma once`, but it is likely to fail in a variety of cases because our filesystem layer currently makes no attempt to normalize/canonicalize paths. Improving the robustness of the solution is left to future work. This change includes a simple test case to confirm that a second `#include` of a file with a `#pragma once` is successfully ignored.
Diffstat (limited to 'source')
-rw-r--r--source/slang/check.cpp16
-rw-r--r--source/slang/diagnostic-defs.h14
-rw-r--r--source/slang/preprocessor.cpp105
3 files changed, 123 insertions, 12 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp
index 95a958495..42bcfe4b5 100644
--- a/source/slang/check.cpp
+++ b/source/slang/check.cpp
@@ -1876,7 +1876,7 @@ namespace Slang
}
return true;
}
-
+
bool validateAttribute(RefPtr<Attribute> attr)
{
if(auto numThreadsAttr = attr.As<NumThreadsAttribute>())
@@ -1939,7 +1939,7 @@ namespace Slang
entryPointAttr->stage = stage;
}
- else if ((attr.As<DomainAttribute>()) ||
+ else if ((attr.As<DomainAttribute>()) ||
(attr.As<MaxTessFactorAttribute>()) ||
(attr.As<OutputTopologyAttribute>()) ||
(attr.As<PartitioningAttribute>()) ||
@@ -1964,7 +1964,7 @@ namespace Slang
// Has no args
SLANG_ASSERT(attr->args.Count() == 0);
}
- else
+ else
{
if(attr->args.Count() == 0)
{
@@ -3639,8 +3639,9 @@ namespace Slang
auto prevResultType = GetResultType(prevFuncDeclRef);
if (!resultType->Equals(prevResultType))
{
- // Bad dedeclaration
- getSink()->diagnose(funcDecl, Diagnostics::unimplemented, "redeclaration has a different return type");
+ // Bad redeclaration
+ getSink()->diagnose(funcDecl, Diagnostics::functionRedeclarationWithDifferentReturnType, funcDecl->getName(), resultType, prevResultType);
+ getSink()->diagnose(prevFuncDecl, Diagnostics::seePreviousDeclarationOf, funcDecl->getName());
// Don't bother emitting other errors at this point
break;
@@ -3671,7 +3672,8 @@ namespace Slang
if (funcDecl->Body && prevFuncDecl->Body)
{
// Redefinition
- getSink()->diagnose(funcDecl, Diagnostics::unimplemented, "function redefinition");
+ getSink()->diagnose(funcDecl, Diagnostics::functionRedefinition, funcDecl->getName());
+ getSink()->diagnose(prevFuncDecl, Diagnostics::seePreviousDefinitionOf, funcDecl->getName());
// Don't bother emitting other errors
break;
@@ -8379,7 +8381,7 @@ namespace Slang
// TODO: We currently do minimal checking here, but this is the
// right place to perform the following validation checks:
//
-
+
// * Are the function input/output parameters and result type
// all valid for the chosen stage? (e.g., there shouldn't be
// an `OutputStream<X>` type in a vertex shader signature)
diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h
index fd836e946..c0c74c78e 100644
--- a/source/slang/diagnostic-defs.h
+++ b/source/slang/diagnostic-defs.h
@@ -42,6 +42,7 @@ DIAGNOSTIC(-1, Note, doYouForgetToMakeComponentAccessible, "do you forget to mak
DIAGNOSTIC(-1, Note, seeDeclarationOf, "see declaration of '$0'")
DIAGNOSTIC(-1, Note, seeOtherDeclarationOf, "see other declaration of '$0'")
+DIAGNOSTIC(-1, Note, seePreviousDeclarationOf, "see previous declaration of '$0'")
//
// 0xxxx - Command line and interaction with host platform APIs.
@@ -67,7 +68,7 @@ DIAGNOSTIC( 12, Error, cannotDeduceSourceLanguage, "can't deduce language for
DIAGNOSTIC( 13, Error, unknownCodeGenerationTarget, "unknown code generation target '$0'");
DIAGNOSTIC( 14, Error, unknownProfile, "unknown profile '$0'");
DIAGNOSTIC( 15, Error, unknownStage, "unknown stage '$0'");
-DIAGNOSTIC( 16, Error, unknownPassThroughTarget, "unknown pass-through target '$0'");
+DIAGNOSTIC( 16, Error, unknownPassThroughTarget, "unknown pass-through target '$0'");
DIAGNOSTIC( 17, Error, unknownCommandLineOption, "unknown command-line option '$0'");
DIAGNOSTIC( 18, Error, noProfileSpecified, "no profile specified; use the '-profile <profile name>' option");
DIAGNOSTIC( 19, Error, multipleEntryPointsNeedMulitpleProfiles, "when multiple entry points are specified, each must have a profile given (with '-profile') before the '-entry' option");
@@ -130,6 +131,10 @@ DIAGNOSTIC(15403, Error, expectedTokenInMacroParameters, "expected '$0' in macro
DIAGNOSTIC(15500, Warning, expectedTokenInMacroArguments, "expected '$0' in macro invocation")
DIAGNOSTIC(15501, Error, wrongNumberOfArgumentsToMacro, "wrong number of arguments to macro (expected $0, got $1)")
+// 156xx - pragmas
+DIAGNOSTIC(15600, Error, expectedPragmaDirectiveName, "expected a name after '#pragma'")
+DIAGNOSTIC(15601, Warning, unknownPragmaDirectiveIgnored, "ignoring unknown directive '#pragma $0'")
+
// 159xx - user-defined error/warning
DIAGNOSTIC(15900, Error, userDefinedError, "#error: $0")
DIAGNOSTIC(15901, Warning, userDefinedWarning, "#warning: $0")
@@ -160,7 +165,6 @@ DIAGNOSTIC(20011, Error, unexpectedColon, "unexpected ':'.")
// 3xxxx - Semantic analysis
//
-DIAGNOSTIC(30001, Error, functionRedefinitionWithArgList, "'$0$1': function redefinition.")
DIAGNOSTIC(30002, Error, parameterAlreadyDefined, "parameter '$0' already defined.")
DIAGNOSTIC(30003, Error, breakOutsideLoop, "'break' must appear inside loop constructs.")
DIAGNOSTIC(30004, Error, continueOutsideLoop, "'continue' must appear inside loop constructs.")
@@ -203,9 +207,15 @@ DIAGNOSTIC(30052, Error, invalidSwizzleExpr, "invalid swizzle pattern '$0' on ty
DIAGNOSTIC(30100, Error, staticRefToNonStaticMember, "type '$0' cannot be used to refer to non-static member '$1'")
+DIAGNOSTIC(30201, Error, functionRedefinition, "function '$0' already has a body")
+DIAGNOSTIC(30202, Error, functionRedeclarationWithDifferentReturnType, "function '$0' declared to return '$1' was previously declared to return '$2'")
+
+
DIAGNOSTIC(33070, Error, expectedFunction, "expression preceding parenthesis of apparent call must have function type.")
DIAGNOSTIC(33071, Error, expectedAStringLiteral, "expected a string literal")
+
+
// Attributes
DIAGNOSTIC(31000, Error, unknownAttributeName, "unknown attribute '$0'")
DIAGNOSTIC(31001, Error, attributeArgumentCountMismatch, "attribute '$0' expects $1 arguments ($2 provided)")
diff --git a/source/slang/preprocessor.cpp b/source/slang/preprocessor.cpp
index 3ec44fc28..71c6fd4bb 100644
--- a/source/slang/preprocessor.cpp
+++ b/source/slang/preprocessor.cpp
@@ -197,6 +197,11 @@ struct Preprocessor
// The translation unit that is being parsed
TranslationUnitRequest* translationUnit;
+ // Any paths that have issued `#pragma once` directives to
+ // stop them from being included again.
+ HashSet<String> pragmaOncePaths;
+
+
TranslationUnitRequest* getTranslationUnit()
{
return translationUnit;
@@ -1608,6 +1613,12 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context)
// a switch of input stream
expectEndOfDirective(context);
+ // Check whether we've previously included this file and seen a `#pragma once` directive
+ if(context->preprocessor->pragmaOncePaths.Contains(foundPath))
+ {
+ return;
+ }
+
// Push the new file onto our stack of input streams
// TODO(tfoley): check if we have made our include stack too deep
@@ -1818,12 +1829,100 @@ static void HandleLineDirective(PreprocessorDirectiveContext* context)
inputStream->primaryStream->lexer.startOverridingSourceLocations(newLoc);
}
+#define SLANG_PRAGMA_DIRECTIVE_CALLBACK(NAME) \
+ void NAME(PreprocessorDirectiveContext* context, Token subDirectiveToken)
+
+// Callback interface used by `#pragma` directives
+typedef SLANG_PRAGMA_DIRECTIVE_CALLBACK((*PragmaDirectiveCallback));
+
+SLANG_PRAGMA_DIRECTIVE_CALLBACK(handleUnknownPragmaDirective)
+{
+ GetSink(context)->diagnose(subDirectiveToken, Diagnostics::unknownPragmaDirectiveIgnored, subDirectiveToken.getName());
+ SkipToEndOfLine(context);
+ return;
+}
+
+SLANG_PRAGMA_DIRECTIVE_CALLBACK(handlePragmaOnceDirective)
+{
+ // We need to identify the path of the file we are preprocessing,
+ // so that we can avoid including it again.
+ //
+ // Note: for now we are doing a very simplistic check where
+ // we use the raw file path as the key for our duplicate checking.
+ //
+ // TODO: a more refined implementation should probably apply Unicode
+ // normalization and case-folding to the path, and then use that
+ // plus a hash of the file contents to determine whether things
+ // represent the "same" file.
+ //
+ // TODO: even for our simplistic implementation, we need to add
+ // logic to deal with `../` segments in path names to detect
+ // trivial cases of the "same" path.
+ //
+ auto directiveLoc = GetDirectiveLoc(context);
+ auto expandedDirectiveLoc = context->preprocessor->translationUnit->compileRequest->getSourceManager()->expandSourceLoc(directiveLoc);
+ String pathIssuedFrom = expandedDirectiveLoc.getSpellingPath();
+
+ context->preprocessor->pragmaOncePaths.Add(pathIssuedFrom);
+}
+
+// Information about a specific `#pragma` directive
+struct PragmaDirective
+{
+ // name of the directive
+ char const* name;
+
+ // Callback to handle the directive
+ PragmaDirectiveCallback callback;
+};
+
+// A simple array of all the `#pragma` directives we know how to handle.
+static const PragmaDirective kPragmaDirectives[] =
+{
+ { "once", &handlePragmaOnceDirective },
+
+ { NULL, NULL },
+};
+
+static const PragmaDirective kUnknownPragmaDirective = {
+ NULL, &handleUnknownPragmaDirective,
+};
+
+// Look up the `#pragma` directive with the given name.
+static PragmaDirective const* findPragmaDirective(String const& name)
+{
+ char const* nameStr = name.Buffer();
+ for (int ii = 0; kPragmaDirectives[ii].name; ++ii)
+ {
+ if (strcmp(kPragmaDirectives[ii].name, nameStr) != 0)
+ continue;
+
+ return &kPragmaDirectives[ii];
+ }
+
+ return &kUnknownPragmaDirective;
+}
+
// Handle a `#pragma` directive
static void HandlePragmaDirective(PreprocessorDirectiveContext* context)
{
- // TODO(tfoley): figure out which pragmas to parse,
- // and which to pass along
- SkipToEndOfLine(context);
+ // Try to read the sub-directive name.
+ Token subDirectiveToken = PeekRawToken(context);
+
+ // The sub-directive had better be an identifier
+ if (subDirectiveToken.type != TokenType::Identifier)
+ {
+ GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::expectedPragmaDirectiveName);
+ SkipToEndOfLine(context);
+ return;
+ }
+ AdvanceRawToken(context);
+
+ // Look up the handler for the sub-directive.
+ PragmaDirective const* subDirective = findPragmaDirective(subDirectiveToken.getName()->text);
+
+ // Apply the sub-directive-specific callback
+ (subDirective->callback)(context, subDirectiveToken);
}
// Handle a `#version` directive