Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ValidateScript does not work for class properties #12517

Open
bluikko opened this issue Apr 29, 2020 · 6 comments
Open

ValidateScript does not work for class properties #12517

bluikko opened this issue Apr 29, 2020 · 6 comments

Comments

@bluikko
Copy link

@bluikko bluikko commented Apr 29, 2020

Steps to reproduce

# not working
class TestClass {
    [ValidateScript({ $_ -le 10 })]
    [Int]$ClassVar
}

Output:

ParserError: 
Line |
   2 |      [ValidateScript({ $_ -le 10 })]
     |                      ~~~~~~~~~~~~~
     | At line:2 char:21 +     [ValidateScript({ $_ -le 10 })] +                     ~~~~~~~~~~~~~
Attribute argument must be a
     | constant.
# working
class TestClass {
    [ValidateRange(0,10)]
    [Int]$ClassVar
}
$cl = [TestClass]::new()
$cl.ClassVar = 10
$cl.ClassVar

Output:

10

Expected behavior

It should be possible to use ValidateScript in classes the same way as for function parameters.

If there is a technical limitation why the behavior is expected then it should be documented somewhere - I could not find anything in documentation about this.

Actual behavior

All validations except ValidateScript work as expected in classes.

Environment data

Name                           Value
----                           -----
PSVersion                      7.0.0
PSEdition                      Core
GitCommitId                    7.0.0
OS                             Microsoft Windows 10.0.18363
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Name                           Value
----                           -----
PSVersion                      5.1.18362.752
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.18362.752
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
@p0W3RH311
Copy link

@p0W3RH311 p0W3RH311 commented Apr 29, 2020

perhaps class is by design static scoping and scriptblock with validateScript is interpreted dynamically...maybe you can create your own attribute with class [System.Management.Automation.ValidateArgumentsAttribute]

@vexx32
Copy link
Collaborator

@vexx32 vexx32 commented Apr 29, 2020

In this specific case you could use [ValidateRange(0, 10)] or [ValidateRange([int]::MinValue, 10)] depending on your needs.

But yeah, classes are handled at parse time, so attributes have some extra restrictions. I'm not sure there's a way around that? Might be, but I would guess there probably isn't much that's really doable there.

@bluikko
Copy link
Author

@bluikko bluikko commented Apr 30, 2020

@vexx32 The simple example is just that - a simple example. My need for ValidateScript is obviously much more complex.

Could you point me to the documentation where this is listed as expected behavior since you are not surprised about this behavior?

I could not find many relevant matches doing a web search about validatescript and class but several did have other people wondering about ValidateScript and this problem. So it would not seem to be very well documented.

It is disappointing. If class methods are not static like this then I might be able to figure out an ugly workaround if I have to.

@vexx32
Copy link
Collaborator

@vexx32 vexx32 commented Apr 30, 2020

I'm mainly not surprised because I've seen it before, both in PS and (more importantly) in C# itself. In compiled code you can't have non-constant/non-primitive values for attributes. It's actually part of the attribute language specification which states:

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • The type object.
  • The type System.Type.
  • An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (Attribute specification).
  • Single-dimensional arrays of the above types.

A constructor argument or public field which does not have one of these types, cannot be used as a positional or named parameter in an attribute specification.

I think there are similar underlying requirements for dynamically compiled assemblies, and .NET in the more general case as well. I'm pretty sure the only reason [ValidateScript()] is even a thing is because PowerShell script can do more or less whatever it want, as long as it's willing to handle things on its own. [ValidateScript()] is possible in PS probably because PS isn't directly compiling functions, it's doing it piecemeal and can handle attributes however differently it wants, more or less. Attribute classes are free to have whatever properties they want, but they can only use the above types in their constructor parameters when they're decorating a class and need to be resolved at compile time.

Classes in PS are a different story entirely, and have to be compiled as-is at parse time, meaning there's no deferred compilation or special handling of attributes for the most part, they just behave very much like C# / .NET attributes.

As far as documenting it in PowerShell, I'd tend to agree that it should be mentioned somewhere in the classes documentation.

@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Apr 30, 2020

/cc @rjmholt

@rjmholt
Copy link
Member

@rjmholt rjmholt commented Apr 30, 2020

Yeah @vexx32 has hit it on the head. It looks like we should handle this more gracefully.

I imagine that this is "expected if you think about the implementation and extrapolate", but has not been anticipated by the existing implementation.

Currently classes are compiled into dynamic .NET assemblies, and won't tolerate attributes that do something that .NET doesn't like, namely use values that can't be embedded into .NET static metadata.

I don't see ValidateScript ever working as an attribute for classes, but I also don't see that as a big issue since classes don't need to do parameter binding. Instead the validation can be done in the method body (similar to how it would be done for a C# method).

I think we should:

  • Emit a parse-time semantic error when this attribute is used on a class method
  • Update documentation to explicitly document how attributes work with classes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.