Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions source/slang/slang-check-expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3782,6 +3782,25 @@ Expr* SemanticsVisitor::CheckInvokeExprWithCheckedOperands(InvokeExpr* expr)
if (funcDeclRefExpr)
funcDeclBase = as<FunctionDeclBase>(funcDeclRefExpr->declRef.getDecl());

if (funcDeclRefExpr)
{
auto knownBuiltinAttrDeclRef = getDeclRef(m_astBuilder, funcDeclRefExpr)
.getDecl()
->findModifier<KnownBuiltinAttribute>();
Comment on lines +3787 to +3789
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Missing null guard and redundant as<> cast

The chain getDeclRef(m_astBuilder, funcDeclRefExpr).getDecl()->findModifier<KnownBuiltinAttribute>() dereferences getDecl() without a null check. Compare with the existing pattern at line 3841-3848 (inside the parameter loop), which stores the DeclRef result and checks if (funcDeclRef) before calling .getDecl()->findModifier<>(). While getDecl() returning null is unlikely after successful overload resolution, the defensive pattern is used elsewhere in this same function.

Additionally, the variable name knownBuiltinAttrDeclRef is misleading — it holds a KnownBuiltinAttribute* (a modifier), not a DeclRef. The subsequent as<KnownBuiltinAttribute>(knownBuiltinAttrDeclRef) on line 3752 is redundant since findModifier<KnownBuiltinAttribute>() already returns KnownBuiltinAttribute*.

Suggestion: Follow the defensive pattern from line 3841+ and simplify:

if (funcDeclRefExpr)
{
    auto funcDeclRef = getDeclRef(m_astBuilder, funcDeclRefExpr);
    if (funcDeclRef)
    {
        if (auto knownBuiltinAttr =
                funcDeclRef.getDecl()->findModifier<KnownBuiltinAttribute>())
        {
            if (auto constantIntVal = as<ConstantIntVal>(knownBuiltinAttr->name))
            {
                if (constantIntVal->getValue() ==
                    (int)KnownBuiltinDeclName::OperatorAddressOf)
                {
                    getSink()->diagnose(
                        Diagnostics::AddressOfOperatorNotSupported{.expr = invoke});
                }
            }
        }
    }
}

if (auto knownBuiltinAttr = as<KnownBuiltinAttribute>(knownBuiltinAttrDeclRef))
Comment on lines +3787 to +3790
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Redundant cast and misleading variable name

Two issues with this pattern:

  1. Misleading name: knownBuiltinAttrDeclRef suggests a DeclRef, but it's actually a KnownBuiltinAttribute* returned by findModifier<KnownBuiltinAttribute>().

  2. Redundant as<> cast: findModifier<KnownBuiltinAttribute>() already returns KnownBuiltinAttribute*, so the subsequent as<KnownBuiltinAttribute>(knownBuiltinAttrDeclRef) is a no-op cast. Compare with the existing pattern at line ~3846 which uses the result directly:

auto knownBuiltinAttr = funcDeclRef.getDecl()->findModifier<KnownBuiltinAttribute>();
if (knownBuiltinAttr) { ... }

Suggested fix: Simplify to match the existing pattern:

Suggested change
auto knownBuiltinAttrDeclRef = getDeclRef(m_astBuilder, funcDeclRefExpr)
.getDecl()
->findModifier<KnownBuiltinAttribute>();
if (auto knownBuiltinAttr = as<KnownBuiltinAttribute>(knownBuiltinAttrDeclRef))
auto knownBuiltinAttr = getDeclRef(m_astBuilder, funcDeclRefExpr)
.getDecl()
->findModifier<KnownBuiltinAttribute>();
if (knownBuiltinAttr)

Comment on lines +3785 to +3790
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Redundant cast, misleading variable name, and missing null guard

Three issues in this block:

  1. Misleading name: knownBuiltinAttrDeclRef implies a DeclRef<Decl>, but it's actually a KnownBuiltinAttribute* (a modifier). The existing code at line ~3884 correctly names this knownBuiltinAttr.

  2. Redundant cast: findModifier<KnownBuiltinAttribute>() already returns KnownBuiltinAttribute* or nullptr. The subsequent as<KnownBuiltinAttribute>(...) on line 3790 is a no-op identity cast. The existing pattern at line ~3887 simply does if (knownBuiltinAttr).

  3. Missing null guard on getDeclRef chain: The existing code at lines 3879–3882 explicitly constructs the DeclRef and checks it before calling .getDecl():

    auto funcDeclRef = funcDeclRefExpr
        ? getDeclRef(m_astBuilder, funcDeclRefExpr)
        : DeclRef<Decl>();
    if (funcDeclRef)
    { ... }

    The new code chains .getDecl()->findModifier<>() directly on the result of getDeclRef() without this guard.

Suggested fix (aligning with the existing pattern at lines 3879–3900):

auto funcDeclRef = getDeclRef(m_astBuilder, funcDeclRefExpr);
if (funcDeclRef)
{
    if (auto knownBuiltinAttr =
            funcDeclRef.getDecl()->findModifier<KnownBuiltinAttribute>())
    {
        if (auto constantIntVal = as<ConstantIntVal>(knownBuiltinAttr->name))
        {
            if (constantIntVal->getValue() ==
                (int)KnownBuiltinDeclName::OperatorAddressOf)
            {
                getSink()->diagnose(
                    Diagnostics::AddressOfOperatorNotSupported{.expr = invoke});
                return CreateErrorExpr(invoke);
            }
        }
    }
}

{
if (auto constantIntVal = as<ConstantIntVal>(knownBuiltinAttr->name))
{
if (constantIntVal->getValue() ==
(int)KnownBuiltinDeclName::OperatorAddressOf)
{
getSink()->diagnose(
Comment on lines +3796 to +3797
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: No early return after diagnosis leads to cascading errors

After emitting error 30087, execution falls through to the parameter validation loop (~line 3766+). For expressions like &constBuffer[x], this produces a cascade of errors:

  1. Error 30087: "address-of operator not supported" (from this new check)
  2. Error 30079: "cannot take address of immutable object" (from the existing CannotTakeConstantPointers check at ~line 3858)
  3. "argument must be l-value" (from ArgumentExpectedLvalue at ~line 3865)

The first error is sufficient — the operator is unconditionally rejected. The subsequent errors are about specific argument constraints of an operator that no longer exists, which is noise.

Suggested fix:

getSink()->diagnose(
    Diagnostics::AddressOfOperatorNotSupported{.expr = invoke});
return CreateErrorExpr(invoke);

This also makes the existing CannotTakeConstantPointers check at lines 3854-3860 dead code for operator& — consider removing it as cleanup.

Diagnostics::AddressOfOperatorNotSupported{.expr = invoke});
Comment on lines +3797 to +3798
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: No early return after E30087 — cascading errors and continued IR lowering

After emitting E30087, execution falls through to the parameter validation loop (line 3804+), which produces additional confusing diagnostics for the same expression. The updated invalid-constant-pointer-taking.slang test confirms this cascade: E30087 is followed by E30079 ("cannot take address of immutable object") plus lvalue errors.

More significantly, the invoke expression retains its original type and is returned as-is. This means IR generation (slang-lower-to-ir.cpp) will attempt to lower the deprecated operator& invocation via kIROp_GetAddress, producing IR for an explicitly prohibited language construct. While the error count will prevent final output, generating IR for rejected code is unnecessary work and could interact poorly with IR passes that assume semantic checking was clean.

Example: float* p = &constant_buffer[i]; produces:

error 30087: the '&' operator for taking addresses is no longer supported
error 30079: Not allowed to take the address of an immutable object
error: argument passed to parameter '0' must be l-value.

Only the first error is the actual issue; the rest are noise.

Suggested fix:

getSink()->diagnose(
    Diagnostics::AddressOfOperatorNotSupported{.expr = invoke});
return CreateErrorExpr(invoke);

This follows the existing pattern in this file and suppresses both cascading diagnostics and IR lowering.

}
Comment on lines +3785 to +3799
}
Comment on lines +3785 to +3800
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Bug: Multiple existing tests use operator& and will break

This unconditional check fires for ALL uses of operator& during semantic checking, regardless of target or argument type. At least 7 existing test files use & for address-of and are not updated by this PR:

  1. tests/diagnostics/invalid-constant-pointer-taking.slang (lines 14, 23) — currently expects errors 30079, will now get 30087 first
  2. tests/cpu-program/pointer-deref.slang (line 17: Record *pRec = &rec;) — EXECUTABLE test, will fail
  3. tests/language-feature/dynamic-dispatch/ptr-to-interface-cast.slang (line 58: Foo* pf = &f;) — CPU+CUDA test
  4. tests/language-feature/dynamic-dispatch/ptr-to-interface-local.slang (line 29: IFoo* p = &local;) — SPIRV test
  5. tests/language-feature/dynamic-dispatch/diagnose-ptr-to-interface-implicit-cast.slang (line 26: Foo* pf = &f;)
  6. tests/language-feature/pointer/coherent-load-store-physical-storage-buffer.slang (line 23)
  7. tests/language-feature/dynamic-dispatch/ptr-to-interface-double-indirect.slang (line 40)

Suggestion: Each of these tests needs to either migrate from &expr to __getAddress(expr), or be converted to diagnostic tests that expect error 30087. This PR should update all affected tests to pass CI.

}
}

Index paramCount = funcType->getParamCount();

for (Index pp = 0; pp < paramCount; ++pp)
Expand Down
7 changes: 7 additions & 0 deletions source/slang/slang-diagnostics.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1389,6 +1389,13 @@ err(
span { loc = "expr:Expr", message = "Not allowed to take the address of an immutable object" }
)

err(
"address-of-operator-not-supported",
30087,
"address-of operator not supported",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Error message does not suggest replacement

The message "the '&' operator for taking addresses is no longer supported in Slang" tells users what's wrong but not what to do instead. Since this is a breaking change, the error message is the primary migration guide most users will see. The PR description states internal code should use __getAddress instead.

Suggestion: Include the replacement in the message:

span { loc = "expr:Expr", message = "the '&' operator for taking addresses is no longer supported in Slang; use '__getAddress()' instead" }

span { loc = "expr:Expr", message = "the '&' operator for taking addresses is no longer supported in Slang" }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Error message lacks migration guidance and docs will become stale

The error message tells users what's wrong but not what to do about it. Since __getAddress(expr) is the replacement, the message should include a migration hint. This is especially important for a breaking change — users hitting this error need an actionable fix.

Additionally, several documentation pages still describe & as the primary way to take addresses:

  • docs/user-guide/03-convenience-features.md (lines 558, 576, 592) — shows &pNext[1], says "Slang can produce pointers using the & operator"
  • docs/language-reference/types-pointer.md (lines 71-72, 106, 171) — says "the address-of operator & is used", shows ptr = &arr[9]

These docs should be updated in this PR or a follow-up to avoid sending users to a now-broken feature.

Suggested fix for the error message:

Suggested change
span { loc = "expr:Expr", message = "the '&' operator for taking addresses is no longer supported in Slang" }
span { loc = "expr:Expr", message = "the '&' operator for taking addresses is no longer supported in Slang; use '__getAddress(expr)' instead" }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Error message lacks migration guidance; documentation still describes & as supported

The error message tells users what's wrong but not what to do instead. Since __getAddress is the designated replacement (per the PR description), the diagnostic should mention it so users can migrate without searching documentation.

Additionally, this is a breaking change but the documentation is not updated in this PR:

  • docs/language-reference/types-pointer.md line 71: "To obtain the address of an object, the address-of operator & is used"
  • docs/language-reference/types-pointer.md lines 106, 171: code examples using &arr[9] and &g_inputData[...]
  • docs/user-guide/03-convenience-features.md line 558: MyType* pNext2 = &pNext[1];
  • docs/user-guide/03-convenience-features.md line 592: "Slang can produce pointers using the & operator from data in global memory"

Suggestion: Update the message to include the replacement:

span { loc = "expr:Expr", message = "the '&' operator for taking addresses is no longer supported in Slang; use '__getAddress' instead" }

And update the documentation files listed above to use __getAddress in descriptions and examples.

)

err(
"cannot-specialize-generic-with-existential",
33180,
Expand Down
11 changes: 11 additions & 0 deletions tests/diagnostics/address-of-operator-deprecated.slang
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -target spirv -entry computeMain -stage compute
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Documentation not updated for this breaking change

Two documentation files still describe & as a supported/primary way to take addresses:

  1. docs/language-reference/types-pointer.md:71 — "To obtain the address of an object, the address-of operator & is used. [...] Alternatively, __getAddress(obj) may be used."
  2. docs/user-guide/03-convenience-features.md:592 — "Slang can produce pointers using the & operator from data in global memory."

Both files present & as the primary method and __getAddress as an alternative, which is the opposite of the new reality. Code examples in types-pointer.md (lines 106, 171) also use & for address-of.

Suggestion: Update these docs in the same PR or a companion PR. At minimum, swap the guidance so __getAddress is primary and & is noted as deprecated/removed.


RWStructuredBuffer<float> buffer;

[shader("compute")]
[numthreads(1,1,1)]
void computeMain(uint3 threadId : SV_DispatchThreadID)
{
// CHECK: error[E30087]: address-of operator not supported
float* p = &buffer[threadId.x];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Multiple existing tests use & for address-of and will fail

Beyond this new test and the updated invalid-constant-pointer-taking.slang, at least 8 other test files in the repo use & as the address-of operator in Slang source (not in CHECK patterns). These will now fail with E30087:

  • tests/spirv/pointer.slang:21return &p.data;
  • tests/bugs/gh-3601.slang:26return &p.data;
  • tests/cpu-program/pointer-deref.slang:17Record *pRec = &rec;
  • tests/language-feature/dynamic-dispatch/ptr-to-interface-local.slang:29IFoo* p = &local;
  • tests/language-feature/dynamic-dispatch/ptr-to-interface-cast.slang:58Foo* pf = &f;
  • tests/language-feature/dynamic-dispatch/diagnose-ptr-to-interface-implicit-cast.slang:26Foo* pf = &f;
  • tests/language-feature/pointer/coherent-load-store-physical-storage-buffer.slang:23&secondPtrIn[1]
  • tests/bugs/gh-7499.slang:23f(&arr, size)

Suggestion: Migrate these tests to use __getAddress(), or convert them to diagnostic tests expecting E30087 where the & usage is the point of the test.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Gap: Limited test coverage and existing tests not updated

Two concerns:

1. Existing tests using & are not updated by this PR:

  • tests/language-feature/dynamic-dispatch/ptr-to-interface-local.slang line 29: IFoo* p = &local;
  • tests/language-feature/dynamic-dispatch/diagnose-ptr-to-interface-implicit-cast.slang lines 39, 55, 59: Foo* pf = &f;, Foo* pf3 = &b;, IBase* ibp = &ib;

These tests will now emit E30087 in addition to their expected errors. They may still pass due to non-exhaustive/filecheck semantics, but they now implicitly test behavior of an errored expression path rather than valid code. They should either be updated to annotate the new E30087, or migrated to __getAddress.

2. Limited scenario coverage: The new test only covers buffer element access (&buffer[threadId.x]). Since this is a language-wide deprecation, consider adding scenarios for local variables (&localVar), struct members (&s.field), and array elements from local arrays. These different expression types may exercise different code paths during overload resolution.

}
4 changes: 4 additions & 0 deletions tests/diagnostics/invalid-constant-pointer-taking.slang
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ StructuredBuffer<uint> constant_uint_buffer;
void computeMain(uint3 threadId : SV_DispatchThreadID)
{
float* mutablePtr1 = &mutable_float_buffer[threadId.x];
//diag: ^ address-of operator not supported
//diag: ^ the '&' operator for taking addresses is no longer supported in Slang

float* mutablePtr2 = __getAddress(mutable_float_buffer[threadId.x]);
//diag: ^^^^^^^^^^^^ invalid __getAddress usage
Expand All @@ -21,6 +23,8 @@ void computeMain(uint3 threadId : SV_DispatchThreadID)

// Constant pointers arent a thing in slang
float* ptr1 = &constant_float_buffer[threadId.x];
//diag: ^ address-of operator not supported
//diag: ^ the '&' operator for taking addresses is no longer supported in Slang
//diag: ^ cannot take address of immutable object
//diag: ^ Not allowed to take the address of an immutable object
//diag: ^ argument must be l-value
Expand Down
Loading