diff --git a/gdb/c-exp.y b/gdb/c-exp.y
index 8e405e4dc41..f152b1d53f0 100644
--- a/gdb/c-exp.y
+++ b/gdb/c-exp.y
@@ -1064,15 +1064,18 @@ block	:	BLOCKNAME
 				   copy_name ($1.stoken).c_str ());
 			  $$.search_namespace = false;
 			  $$.b_val = $1.sym.symbol->value_block ();
+			  pstate->reset_linker_namespace ();
 			}
 	|	FILENAME
 			{
 			  $$ = $1;
+			  pstate->reset_linker_namespace ();
 			}
 	|	DOUBLESQUAREOPEN INT DOUBLESQUARECLOSE
 			{
 			  $$.search_namespace = true;
 			  $$.namespace_val = $2.val;
+			  pstate->set_linker_namespace ($2.val);
 			}
 	;
 
@@ -1089,12 +1092,26 @@ block	:	block COLONCOLON name
 						 SEARCH_FUNCTION_DOMAIN,
 						 nullptr);
 
-			  if (tem.symbol == nullptr)
+			  if (tem.symbol == nullptr && !$1.search_namespace)
 			    error (_("No function \"%ps\" in specified context."),
 				   styled_string (function_name_style.style (),
 						  copy.c_str ()));
+			  else if (tem.symbol == nullptr && $1.search_namespace)
+			    /* COPY can be a function or a file.  There is no way
+			       to identify which the user intended, so emit a
+			       generic warning instead.  */
+			    error (_("Nothing named \"%s\" in specified context."),
+				   copy.c_str ());
 			  $$.b_val = tem.symbol->value_block ();
 			  $$.search_namespace = false;
+			  pstate->reset_linker_namespace ();
+			}
+	|	block COLONCOLON FILENAME
+			{
+			    if (!$1.search_namespace)
+				error (_("Filename must be the first part of the expression"));
+			    $$ = $3;
+			  pstate->reset_linker_namespace ();
 			}
 	;
 
@@ -1131,6 +1148,7 @@ variable:	block COLONCOLON name
 			    pstate->block_tracker->update (sym);
 
 			  pstate->push_new<var_value_operation> (sym);
+			  pstate->reset_linker_namespace ();
 			}
 	;
 
@@ -3207,7 +3225,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
 	  || is_quoted_name)
 	{
 	  /* See if it's a file name. */
-	  if (auto symtab = lookup_symtab (current_program_space, copy.c_str ());
+	  if (auto symtab = lookup_symtab (current_program_space, copy.c_str (),
+					   par_state->get_linker_namespace ());
 	      symtab != nullptr)
 	    {
 	      yylval.bval.b_val
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 5371d8426ae..38a30f04f77 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -3628,11 +3628,11 @@ collect_symtabs_from_filename (const char *file,
 	  if (pspace->executing_startup)
 	    continue;
 
-	  for_each_symtab (pspace, file, collector);
+	  for_each_symtab (pspace, file, -1, collector);
 	}
     }
   else
-    for_each_symtab (search_pspace, file, collector);
+    for_each_symtab (search_pspace, file, -1, collector);
 
   /* It is tempting to use the unordered_dense 'extract' method here,
      and remove the separate vector -- but it's unclear if ordering
diff --git a/gdb/parse.c b/gdb/parse.c
index fd190d6e15e..2ed52d689a1 100644
--- a/gdb/parse.c
+++ b/gdb/parse.c
@@ -426,7 +426,8 @@ parse_exp_in_context (const char **stringptr, CORE_ADDR pc,
 
   parser_state ps (lang, get_current_arch (), expression_context_block,
 		   expression_context_pc, flags, *stringptr,
-		   completer != nullptr, tracker);
+		   completer != nullptr, tracker,
+		   current_program_space->solib_ops ());
 
   scoped_restore_current_language lang_saver (lang->la_language);
 
diff --git a/gdb/parser-defs.h b/gdb/parser-defs.h
index a424f0c7b6f..299fd2b74af 100644
--- a/gdb/parser-defs.h
+++ b/gdb/parser-defs.h
@@ -26,6 +26,7 @@
 #include "expression.h"
 #include "symtab.h"
 #include "expop.h"
+#include "solib.h"
 
 struct block;
 struct language_defn;
@@ -147,7 +148,8 @@ struct parser_state : public expr_builder
 		parser_flags flags,
 		const char *input,
 		bool completion,
-		innermost_block_tracker *tracker)
+		innermost_block_tracker *tracker,
+		const solib_ops *ops)
     : expr_builder (lang, gdbarch),
       expression_context_block (context_block),
       expression_context_pc (context_pc),
@@ -157,7 +159,8 @@ struct parser_state : public expr_builder
       comma_terminates ((flags & PARSER_COMMA_TERMINATES) != 0),
       parse_completion (completion),
       void_context_p ((flags & PARSER_VOID_CONTEXT) != 0),
-      debug ((flags & PARSER_DEBUG) != 0)
+      debug ((flags & PARSER_DEBUG) != 0),
+      m_solib_ops (ops)
   {
   }
 
@@ -267,6 +270,26 @@ struct parser_state : public expr_builder
     push (expr::make_operation<T> (std::move (lhs), std::move (rhs)));
   }
 
+  void set_linker_namespace (LONGEST ns_id)
+  {
+    if (m_solib_ops == nullptr)
+      error (_("Linker namespaces require an active inferior"));
+    if (m_solib_ops->supports_namespaces ())
+      m_linker_namespace = ns_id;
+    else
+      error (_("Linker namespaces are not supported"));
+  }
+
+  void reset_linker_namespace ()
+  {
+    m_linker_namespace = -1;
+  }
+
+  LONGEST get_linker_namespace () const
+  {
+    return m_linker_namespace;
+  }
+
   /* Function called from the various parsers' yyerror functions to throw
      an error.  The error will include a message identifying the location
      of the error within the current expression.  */
@@ -327,6 +350,16 @@ struct parser_state : public expr_builder
 
   /* Stack of operations.  */
   std::vector<expr::operation_up> m_operations;
+
+  /* If the expression is being restricted to a specific namespace, this is
+     where that information is stored for the block lookup.  It should be
+     accessed through setter/getters to ensure that the linker namespace is
+     only set when the gdbarch supports it.  */
+  LONGEST m_linker_namespace = -1;
+
+  /* Used to figure out if an inferior is capable of handling linker
+     namespaces at all.  */
+  const solib_ops *m_solib_ops;
 };
 
 /* A string token, either a char-string or bit-string.  Char-strings are
diff --git a/gdb/rust-parse.c b/gdb/rust-parse.c
index 0c4f2a501ab..728fc88842b 100644
--- a/gdb/rust-parse.c
+++ b/gdb/rust-parse.c
@@ -2305,7 +2305,8 @@ rust_lex_tests (void)
 {
   /* Set up dummy "parser", so that rust_type works.  */
   parser_state ps (language_def (language_rust), current_inferior ()->arch (),
-		   nullptr, 0, 0, nullptr, 0, nullptr);
+		   nullptr, 0, 0, nullptr, 0, nullptr,
+		   current_program_space->solib_ops ());
   rust_parser parser (&ps);
 
   rust_lex_test_one (&parser, "", 0);
diff --git a/gdb/symtab.c b/gdb/symtab.c
index 5eab8ad600a..d7317a758b1 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -630,14 +630,11 @@ compare_filenames_for_search (const char *filename, const char *search_name)
 	      && STRIP_DRIVE_SPEC (filename) == &filename[len - search_len]));
 }
 
-/* Return the first symtab in PSPACE matching NAME and for which
-   CALLBACK returns true.
-
-   See documentation for for_each_symtab for how exactly NAME is matched.  */
+/* See symtab.h.  */
 
 static symtab *
 find_symtab (program_space *pspace, const char *name,
-	     find_symtab_callback_ftype callback)
+	     LONGEST linker_ns, find_symtab_callback_ftype callback)
 {
   gdb::unique_xmalloc_ptr<char> real_path;
 
@@ -649,10 +646,23 @@ find_symtab (program_space *pspace, const char *name,
       gdb_assert (IS_ABSOLUTE_PATH (real_path.get ()));
     }
 
-  for (objfile &objfile : pspace->objfiles ())
+  std::vector<objfile *> objfiles_to_search;
+  if (linker_ns >= 0)
+    {
+      gdb_assert (pspace->solib_ops ()->supports_namespaces ());
+      objfiles_to_search
+	= get_objfiles_in_linker_namespace (linker_ns, pspace);
+    }
+  else
+    {
+      for (objfile &objf : pspace->objfiles ())
+	objfiles_to_search.push_back (&objf);
+    }
+
+  for (objfile *objfile : objfiles_to_search)
     if (symtab *result
-	  = objfile.find_symtab_matching_filename (name, real_path.get (),
-						   callback);
+	  = objfile->find_symtab_matching_filename (name, real_path.get (),
+						    callback);
 	result != nullptr)
       return result;
 
@@ -663,9 +673,9 @@ find_symtab (program_space *pspace, const char *name,
 
 void
 for_each_symtab (program_space *pspace, const char *name,
-		 for_each_symtab_callback_ftype callback)
+		 LONGEST linker_ns, for_each_symtab_callback_ftype callback)
 {
-  find_symtab (pspace, name, [&] (symtab *symtab)
+  find_symtab (pspace, name, linker_ns, [&] (symtab *symtab)
 	       {
 		 callback (symtab);
 		 return false;
@@ -675,9 +685,10 @@ for_each_symtab (program_space *pspace, const char *name,
 /* See symtab.h.  */
 
 symtab *
-lookup_symtab (program_space *pspace, const char *name)
+lookup_symtab (program_space *pspace, const char *name, LONGEST linker_ns)
 {
-  return find_symtab (pspace, name, [&] (symtab *symtab) { return true; });
+  return find_symtab (pspace, name, linker_ns,
+		      [&] (symtab *symtab) { return true; });
 }
 
 
@@ -6192,7 +6203,7 @@ collect_file_symbol_completion_matches (completion_tracker &tracker,
 
   /* Go through symtabs for SRCFILE and check the externs and statics
      for symbols which match.  */
-  for_each_symtab (current_program_space, srcfile, [&] (symtab *s)
+  for_each_symtab (current_program_space, srcfile, -1, [&] (symtab *s)
     {
       add_symtab_completions (s->compunit (),
 			      tracker, mode, lookup_name,
diff --git a/gdb/symtab.h b/gdb/symtab.h
index 3feac5fcf6c..f73aedd9c02 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -2074,7 +2074,8 @@ const char *multiple_symbols_select_mode (void);
 
 /* Lookup a symbol table in PSPACE by source file name.  */
 
-extern symtab *lookup_symtab (program_space *pspace, const char *name);
+extern symtab *lookup_symtab (program_space *pspace, const char *name,
+			      LONGEST linker_ns = -1);
 
 /* An object of this type is passed as the 'is_a_field_of_this'
    argument to lookup_symbol and lookup_symbol_in_language.  */
@@ -2806,6 +2807,7 @@ using for_each_symtab_callback_ftype = std::function<void (symtab *)>;
    Call CALLBACK with each symtab that is found.  */
 
 void for_each_symtab (program_space *pspace, const char *name,
+		      LONGEST linker_ns,
 		      for_each_symtab_callback_ftype callback);
 
 /* Callback type for function find_symtab.  */
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
index 02b2bbe6417..35c5124a65e 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
@@ -24,6 +24,8 @@
 #include <unistd.h>
 #include <stdio.h>
 
+int global_main_file = 1312;
+
 int
 main (void)
 {
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
index 72b92304325..78daed4518e 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
@@ -321,9 +321,15 @@ proc_with_prefix test_print_namespace_symbol {} {
     clean_restart
     gdb_load $::binfile
 
+    set ns0 [ns_id_for_command 0]
     set ns1 [ns_id_for_command 1]
     set ns2 [ns_id_for_command 2]
 
+    # Test printing variables before starting the inferior
+    gdb_test "print ${ns1}::gdb_dlmopen_glob" \
+	"Linker namespaces require an active inferior" \
+	"Before starting inferior"
+
     if { ![runto_main] } {
 	return
     }
@@ -355,12 +361,27 @@ proc_with_prefix test_print_namespace_symbol {} {
     gdb_test "print ${ns2}::func_with_other_call::n" \
 	"No frame is currently executing in block func_with_other_call."
 
+    # Test that complex expressions work with multiple namespaces.
+    # At this point, the global in namespace 1 is 0, and in namespace 2 is 1
+    # and the current namespace is 1.
+    gdb_test "print ${ns2}::gdb_dlmopen_glob + gdb_dlmopen_glob" ".* = 1"
+    gdb_test "print ${ns2}::'${::srcfile_lib}'::gdb_dlmopen_glob + gdb_dlmopen_glob" \
+	".* = 1"
+    gdb_test "print ${ns2}::'${::srcfile_lib}'::gdb_dlmopen_glob + '${::srcfile}'::global_main_file" \
+	".* = 1312"
+
     # Leave to default namespace.
     gdb_continue_to_breakpoint "TAG: first dlclose"
     # This global doesn't exist in the default namespace.  Rather than
     # returning a random one, we just say we didn't find one.
     gdb_test "print gdb_dlmopen_glob" \
 	"No symbol .gdb_dlmopen_glob. in the current linker namespace."
+
+    # Minimal testing for finding files in namespaces.
+    gdb_test "print ${ns1}::'${::srcfile_lib}'::gdb_dlmopen_glob" \
+	".* = 2"
+    gdb_test "print ${ns0}::'${::srcfile_lib}'::gdb_dlmopen_glob" \
+	"Nothing named .${::srcfile_lib}. in specified context."
 }
 
 test_info_shared
