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

Using namespace doesn't work for OutputType in Modules #13768

Open
iRon7 opened this issue Oct 10, 2020 · 11 comments
Open

Using namespace doesn't work for OutputType in Modules #13768

iRon7 opened this issue Oct 10, 2020 · 11 comments
Labels
Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a WG-Engine core PowerShell engine, interpreter, and runtime

Comments

@iRon7
Copy link

iRon7 commented Oct 10, 2020

After creating a module out of a cmdlet, it appeared that I had to add the Add-Type -AssemblyName to resolve long .Net types.
All shorthand types are now resolved as expected, except for the OutputType attribute in the [CmdletBinding()]:

When dot-source the file below as a PowerShell script (. .\OutputType.ps1 file without the Export-ModuleMember) it works fine, but when converted into a module, the shorten type (RuntimeDefinedParameterDictionary) doesn't work for the OutputType:

Steps to reproduce

Module (OutputType.psm1) file:

using namespace System.Management.Automation

function Test {
    [CmdletBinding()][OutputType([RuntimeDefinedParameterDictionary])] param(
        [RuntimeDefinedParameterDictionary[]]$Params
    )
    Process {
        [RuntimeDefinedParameterDictionary]::new()
    }
}
Export-ModuleMember -Function * -Alias *
Import-Module .\OutputType.psm1
test

Expected behavior

No error expected

Actual behavior

InvalidOperation: C:\OutputType.psm1:4
Line |
   4 |  … dletBinding()][OutputType([RuntimeDefinedParameterDictionary])] param …
     |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Unable to find type [RuntimeDefinedParameterDictionary].

Workaround

Add full qualified type name to the OutputType attribute:

[CmdletBinding()][OutputType([System.Management.Automation.RuntimeDefinedParameterDictionary])] param( ...

Environment data

Name                           Value
----                           -----
PSVersion                      7.0.3
PSEdition                      Core
GitCommitId                    7.0.3
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
@iRon7 iRon7 added the Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a label Oct 10, 2020
@vexx32
Copy link
Collaborator

vexx32 commented Oct 10, 2020

Add-Type won't generally resolve .NET types. You shouldn't need to call Add-Type for System.Management.Automation either; that assembly is always loaded because it's PowerShell's own primary assembly. 🤔

If anything, you might want a using namespace System.Management.Automation to allow you to shorten type names.

@iRon7 iRon7 changed the title Using namespace/Add-Type -AssemblyName doesn't work for OutputType in Modules Using namespace doesn't work for OutputType in Modules Oct 11, 2020
@iRon7
Copy link
Author

iRon7 commented Oct 11, 2020

@vexx32, thanks for the comment.
I added the add-type as one of my first attempt to resolve the issue, but never tried to undo it. 🥴
Anyways, it does not detract from the actual issue (I have changed the title and description).

@jhoneill
Copy link

I've always understood it would work this way.

If you import the the psm1 file, and then try [RuntimeDefinedParameterDictionary]::new() at the prompt you see the effect of the using namespace statement is not global. In places like C# I've always though of "using" being like a path for executables, and telling the compiler to try prefixes on unresolved names in that file and they don't apply anywhere else. PowerShell doesn't work like that in all cases. If you put using namespace in a ps1 file and dot source it, that namespace is seen globally, but if you have it in a psm1 file and import the module, it isn't. There are similar issues with classes written in PowerShell and loaded from modules, which depending how they are loaded may or may not be visible outside. So things like argument completers can break going from a free-standing ps1 being dot sourced, to same file being loaded as part of a module.

@iRon7
Copy link
Author

iRon7 commented Oct 11, 2020

@jhoneill, thank you for the explanation.
I was expecting something like this but wanted at least make note of the limitation. I don't mind if this issue is closed in won't fix.

@jhoneill
Copy link

@iRon7 :-) Things don't work the way most people would expect them to. The question, really, is how practical it is to change it to be logical.

The using namespace statements (and PS-Defined classes) that are in scope when PowerShell examines a function before running it, and the ones that are in scope when it executes the code in the function body can be different when they get loaded as a module (depending on exactly how the module is done) and I think to most people that's feels wrong.

@vexx32
Copy link
Collaborator

vexx32 commented Oct 11, 2020

Yeah, it's a scoped action; it only applies to the scope it's used for. You can scope it to a file, which might be a function, or a module, or a script. But unless you dot source that file (which merges the scope into your current scope) it doesn't apply outside that file.

That said, I would tend to agree it should probably work for modules more consistently. I've seen a few less consistent issues with it in modules in the past... but I've also seen plenty of cases where this does work just fine (I use it in PSKoans in at least one or two functions).

I wonder if the difference is that [outputtype] (along with other function attributes like [argumentcompleter]) are evaluated in the global scope or something for some reason? @SeeminglyScience might have some idea there.

@SeeminglyScience
Copy link
Collaborator

@vexx32 yeah, it's whatever scope the attribute happens to be compiled in. Very unlikely to be the scope where the type is resolvable.

Part of the problem is that ITypeName.GetReflectionType() doesn't really have any visibility into the AST, so it can't check what the current using statements are. Nor does it have any visibility into what types are defined in that scope. Most of the type using statements only work based on SessionStateScope.TypeResolutionState.

If ITypeName kept track of what using statements were defined in it's AST (currently it only tracks it's IScriptExtent) then using statements could properly be "file scoped". That wouldn't really solve the problem of defined types (except in some cases with using module maybe, though probably better to limit to using namespace) but would be nice.

@jhoneill
Copy link

I wonder if the difference is that [outputtype] (along with other function attributes like [argumentcompleter]) are evaluated in the global scope or something for some reason? @SeeminglyScience might have some idea there.

I'm not sure I completely understand the reply. But the way I understood it was when (for example) you type
command -param X | select -property [tab], the scope where something is asking for the types that are attributes of -param and asking "What is the output type of command" is a different scope from the one where command is defined. Command crosses scope but the using statement doesn't and class might or might not depending how the module loaded it. Since classes loaded by add-type are global, I think it's OK for classes defined with a powershell class statement to be as well, but just push all used namespaces into the global scope could end up with name clashes and whether there is a risk the wrong namespace wins. It almost needs an "export namespace" command.

@SeeminglyScience
Copy link
Collaborator

the scope where something is asking for the types that are attributes of -param and asking "What is the output type of command" is a different scope from the one where command is defined.

Yeah pretty much. That information is queried from the current interactive scope.

Command crosses scope but the using statement doesn't and class might or might not depending how the module loaded it.

It's less about whether using does, and more about how tab completion doesn't.

Since classes loaded by add-type are global, I think it's OK for classes defined with a powershell class statement to be as well

Without the ability to declare a namespace that would end with a lot of conflicts. For example it'd be interesting to see how many modules have a class named Error.

but just push all used namespaces into the global scope could end up with name clashes and whether there is a risk the wrong namespace wins. It almost needs an "export namespace" command.

I'm not sure the solution is to change the state of global, I'd rather see tab completion become aware of the callee's state.

@iSazonov iSazonov added the WG-Engine core PowerShell engine, interpreter, and runtime label Oct 11, 2020
@iSazonov
Copy link
Collaborator

I guess we already have such issue.

@iRon7
Copy link
Author

iRon7 commented Nov 16, 2023

The still exists:

$PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.3.9
PSEdition                      Core
GitCommitId                    7.3.9
OS                             Microsoft Windows 10.0.19045
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

@microsoft-github-policy-service microsoft-github-policy-service bot removed the Resolution-No Activity Issue has had no activity for 6 months or more label Nov 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a WG-Engine core PowerShell engine, interpreter, and runtime
Projects
None yet
Development

No branches or pull requests

5 participants